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:ak@nuxeo.com">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 public AzureFileStorage(CloudBlobContainer container) { 052 this.container = container; 053 } 054 055 @Override 056 public void storeFile(String digest, File file) throws IOException { 057 long t0 = 0; 058 if (log.isDebugEnabled()) { 059 t0 = System.currentTimeMillis(); 060 log.debug("storing blob " + digest + " to Azure"); 061 } 062 CloudBlockBlob blob; 063 try { 064 blob = container.getBlockBlobReference(digest); 065 if (blob.exists()) { 066 if (isBlobDigestCorrect(digest, blob)) { 067 if (log.isDebugEnabled()) { 068 log.debug("blob " + digest + " is already in Azure"); 069 } 070 return; 071 } 072 } 073 074 try (InputStream is = new FileInputStream(file)) { 075 blob.upload(is, file.length()); 076 } 077 } catch (StorageException | URISyntaxException e) { 078 throw new IOException(e); 079 } finally { 080 if (log.isDebugEnabled()) { 081 long dtms = System.currentTimeMillis() - t0; 082 log.debug("stored blob " + digest + " to Azure in " + dtms + "ms"); 083 } 084 } 085 } 086 087 @Override 088 public boolean fetchFile(String digest, File file) throws IOException { 089 long t0 = 0; 090 if (log.isDebugEnabled()) { 091 t0 = System.currentTimeMillis(); 092 log.debug("fetching blob " + digest + " from Azure"); 093 } 094 try { 095 CloudBlockBlob blob = container.getBlockBlobReference(digest); 096 if (!(blob.exists() && isBlobDigestCorrect(digest, blob))) { 097 log.error("Invalid ETag in Azure, AzDigest=" + blob.getProperties().getContentMD5() + " digest=" 098 + digest); 099 return false; 100 } 101 try (OutputStream os = new FileOutputStream(file)) { 102 blob.download(os); 103 } 104 return true; 105 } catch (URISyntaxException e) { 106 throw new IOException(e); 107 } catch (StorageException e) { 108 return false; 109 } finally { 110 if (log.isDebugEnabled()) { 111 long dtms = System.currentTimeMillis() - t0; 112 log.debug("fetched blob " + digest + " from Azure in " + dtms + "ms"); 113 } 114 } 115 } 116 117 protected static boolean isMissingKey(StorageException e) { 118 return e.getErrorCode().equals(StorageErrorCode.RESOURCE_NOT_FOUND.toString()); 119 } 120 121 protected static boolean isBlobDigestCorrect(String digest, CloudBlockBlob blob) { 122 return isBlobDigestCorrect(digest, blob.getProperties().getContentMD5()); 123 } 124 125 protected static boolean isBlobDigestCorrect(String digest, String contentMD5) { 126 return digest.equals(decodeContentMD5(contentMD5)); 127 } 128 129 protected static String decodeContentMD5(String contentMD5) { 130 try { 131 byte[] bytes = Base64.decode(contentMD5); 132 return Hex.encodeHexString(bytes); 133 } catch (IllegalArgumentException e) { 134 return null; 135 } 136 } 137}