001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo 018 */ 019 020package org.nuxeo.ecm.blob.azure; 021 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.net.URISyntaxException; 029 030import org.apache.commons.codec.binary.Hex; 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.ecm.core.blob.binary.FileStorage; 034 035import com.microsoft.azure.storage.StorageErrorCode; 036import com.microsoft.azure.storage.StorageException; 037import com.microsoft.azure.storage.blob.CloudBlobContainer; 038import com.microsoft.azure.storage.blob.CloudBlockBlob; 039import com.microsoft.azure.storage.core.Base64; 040 041/** 042 * @author <a href="mailto:[email protected]">Arnaud Kervern</a> 043 * @since 7.10 044 */ 045public class AzureFileStorage implements FileStorage { 046 047 private static final Log log = LogFactory.getLog(AzureFileStorage.class); 048 049 protected CloudBlobContainer container; 050 051 protected String prefix; 052 053 public AzureFileStorage(CloudBlobContainer container, String prefix) { 054 this.container = container; 055 this.prefix = prefix; 056 } 057 058 @Override 059 public void storeFile(String digest, File file) throws IOException { 060 long t0 = 0; 061 if (log.isDebugEnabled()) { 062 t0 = System.currentTimeMillis(); 063 log.debug("storing blob " + digest + " to Azure"); 064 } 065 CloudBlockBlob blob; 066 try { 067 blob = container.getBlockBlobReference(prefix + digest); 068 if (blob.exists()) { 069 if (isBlobDigestCorrect(digest, blob)) { 070 if (log.isDebugEnabled()) { 071 log.debug("blob " + digest + " is already in Azure"); 072 } 073 return; 074 } 075 } 076 077 try (InputStream is = new FileInputStream(file)) { 078 blob.upload(is, file.length()); 079 } 080 } catch (StorageException | URISyntaxException e) { 081 throw new IOException(e); 082 } finally { 083 if (log.isDebugEnabled()) { 084 long dtms = System.currentTimeMillis() - t0; 085 log.debug("stored blob " + digest + " to Azure in " + dtms + "ms"); 086 } 087 } 088 } 089 090 @Override 091 public boolean fetchFile(String digest, File file) throws IOException { 092 long t0 = 0; 093 if (log.isDebugEnabled()) { 094 t0 = System.currentTimeMillis(); 095 log.debug("fetching blob " + digest + " from Azure"); 096 } 097 try { 098 CloudBlockBlob blob = container.getBlockBlobReference(prefix + digest); 099 if (!(blob.exists() && isBlobDigestCorrect(digest, blob))) { 100 log.error("Invalid ETag in Azure, AzDigest=" + blob.getProperties().getContentMD5() + " digest=" 101 + digest); 102 return false; 103 } 104 try (OutputStream os = new FileOutputStream(file)) { 105 blob.download(os); 106 } 107 return true; 108 } catch (URISyntaxException e) { 109 throw new IOException(e); 110 } catch (StorageException e) { 111 return false; 112 } finally { 113 if (log.isDebugEnabled()) { 114 long dtms = System.currentTimeMillis() - t0; 115 log.debug("fetched blob " + digest + " from Azure in " + dtms + "ms"); 116 } 117 } 118 } 119 120 protected static boolean isMissingKey(StorageException e) { 121 return e.getErrorCode().equals(StorageErrorCode.RESOURCE_NOT_FOUND.toString()); 122 } 123 124 protected static boolean isBlobDigestCorrect(String digest, CloudBlockBlob blob) { 125 return isBlobDigestCorrect(digest, blob.getProperties().getContentMD5()); 126 } 127 128 protected static boolean isBlobDigestCorrect(String digest, String contentMD5) { 129 return digest.equals(decodeContentMD5(contentMD5)); 130 } 131 132 protected static String decodeContentMD5(String contentMD5) { 133 try { 134 byte[] bytes = Base64.decode(contentMD5); 135 return Hex.encodeHexString(bytes); 136 } catch (IllegalArgumentException e) { 137 return null; 138 } 139 } 140}