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.IOException; 023import java.net.URI; 024import java.net.URISyntaxException; 025import java.security.InvalidKeyException; 026import java.time.Instant; 027import java.time.LocalDateTime; 028import java.time.ZoneId; 029import java.util.Collection; 030import java.util.Date; 031 032import javax.servlet.http.HttpServletRequest; 033 034import org.apache.commons.lang.StringUtils; 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.nuxeo.ecm.blob.AbstractCloudBinaryManager; 038import org.nuxeo.ecm.core.blob.ManagedBlob; 039import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector; 040import org.nuxeo.ecm.core.blob.binary.FileStorage; 041 042import com.microsoft.azure.storage.CloudStorageAccount; 043import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; 044import com.microsoft.azure.storage.StorageException; 045import com.microsoft.azure.storage.blob.CloudBlobClient; 046import com.microsoft.azure.storage.blob.CloudBlobContainer; 047import com.microsoft.azure.storage.blob.CloudBlockBlob; 048import com.microsoft.azure.storage.blob.SharedAccessBlobHeaders; 049import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; 050 051/** 052 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 053 * @since 7.10 054 */ 055public class AzureBinaryManager extends AbstractCloudBinaryManager { 056 057 private static final Log log = LogFactory.getLog(AzureBinaryManager.class); 058 059 private final static String STORAGE_CONNECTION_STRING = "DefaultEndpointsProtocol=%s;" + "AccountName=%s;" 060 + "AccountKey=%s"; 061 062 public static final String ENDPOINT_PROTOCOL_PROPERTY = "endpointProtocol"; 063 064 public final static String SYSTEM_PROPERTY_PREFIX = "nuxeo.storage.azure"; 065 066 public static final String ACCOUNT_NAME_PROPERTY = "account.name"; 067 068 public static final String ACCOUNT_KEY_PROPERTY = "account.key"; 069 070 public static final String CONTAINER_PROPERTY = "container"; 071 072 protected CloudStorageAccount storageAccount; 073 074 protected CloudBlobClient blobClient; 075 076 protected CloudBlobContainer container; 077 078 @Override 079 protected String getSystemPropertyPrefix() { 080 return SYSTEM_PROPERTY_PREFIX; 081 } 082 083 @Override 084 protected void setupCloudClient() throws IOException { 085 if (StringUtils.isBlank(properties.get(AzureBinaryManager.ACCOUNT_KEY_PROPERTY))) { 086 properties.put(AzureBinaryManager.ACCOUNT_NAME_PROPERTY, System.getenv("AZURE_STORAGE_ACCOUNT")); 087 properties.put(AzureBinaryManager.ACCOUNT_KEY_PROPERTY, System.getenv("AZURE_STORAGE_ACCESS_KEY")); 088 } 089 090 String connectionString = String.format(STORAGE_CONNECTION_STRING, 091 getProperty(ENDPOINT_PROTOCOL_PROPERTY, "https"), getProperty(ACCOUNT_NAME_PROPERTY), 092 getProperty(ACCOUNT_KEY_PROPERTY)); 093 try { 094 storageAccount = CloudStorageAccount.parse(connectionString); 095 096 blobClient = storageAccount.createCloudBlobClient(); 097 container = blobClient.getContainerReference(getProperty(CONTAINER_PROPERTY)); 098 container.createIfNotExists(); 099 } catch (URISyntaxException | InvalidKeyException | StorageException e) { 100 throw new IOException("Unable to initialize Azure binary manager", e); 101 } 102 } 103 104 protected BinaryGarbageCollector instantiateGarbageCollector() { 105 return new AzureGarbageCollector(this); 106 } 107 108 protected FileStorage getFileStorage() { 109 return new AzureFileStorage(container); 110 } 111 112 @Override 113 protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException { 114 try { 115 CloudBlockBlob blockBlobReference = container.getBlockBlobReference(digest); 116 SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); 117 policy.setPermissionsFromString("r"); 118 119 Instant endDateTime = LocalDateTime.now() 120 .plusSeconds(directDownloadExpire) 121 .atZone(ZoneId.systemDefault()) 122 .toInstant(); 123 policy.setSharedAccessExpiryTime(Date.from(endDateTime)); 124 125 SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders(); 126 headers.setContentDisposition(getContentDispositionHeader(blob, servletRequest)); 127 headers.setContentType(getContentTypeHeader(blob)); 128 129 String sas = blockBlobReference.generateSharedAccessSignature(policy, headers, null); 130 131 CloudBlockBlob signedBlob = new CloudBlockBlob(blockBlobReference.getUri(), 132 new StorageCredentialsSharedAccessSignature(sas)); 133 return signedBlob.getQualifiedUri(); 134 } catch (URISyntaxException | InvalidKeyException | StorageException e) { 135 throw new IOException(e); 136 } 137 } 138 139 protected void removeBinary(String digest) { 140 try { 141 container.getBlockBlobReference(digest).delete(); 142 } catch (StorageException | URISyntaxException e) { 143 log.error("Unable to remove binary " + digest, e); 144 } 145 } 146 147 @Override 148 public void removeBinaries(Collection<String> digests) { 149 digests.forEach(this::removeBinary); 150 } 151}