001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo 016 */ 017 018package org.nuxeo.ecm.blob.azure; 019 020import java.io.IOException; 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.security.InvalidKeyException; 024import java.time.Instant; 025import java.time.LocalDateTime; 026import java.time.ZoneId; 027import java.util.Collection; 028import java.util.Date; 029 030import javax.servlet.http.HttpServletRequest; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.blob.AbstractCloudBinaryManager; 036import org.nuxeo.ecm.core.blob.ManagedBlob; 037import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector; 038import org.nuxeo.ecm.core.blob.binary.FileStorage; 039 040import com.microsoft.azure.storage.CloudStorageAccount; 041import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; 042import com.microsoft.azure.storage.StorageException; 043import com.microsoft.azure.storage.blob.CloudBlobClient; 044import com.microsoft.azure.storage.blob.CloudBlobContainer; 045import com.microsoft.azure.storage.blob.CloudBlockBlob; 046import com.microsoft.azure.storage.blob.SharedAccessBlobHeaders; 047import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy; 048 049/** 050 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 051 * @since 7.10 052 */ 053public class AzureBinaryManager extends AbstractCloudBinaryManager { 054 055 private static final Log log = LogFactory.getLog(AzureBinaryManager.class); 056 057 private final static String STORAGE_CONNECTION_STRING = "DefaultEndpointsProtocol=%s;" + "AccountName=%s;" 058 + "AccountKey=%s"; 059 060 public static final String ENDPOINT_PROTOCOL_PROPERTY = "endpointProtocol"; 061 062 public final static String PROPERTIES_PREFIX = "nuxeo.storage.azure"; 063 064 public static final String ACCOUNT_NAME_PROPERTY = "account.name"; 065 066 public static final String ACCOUNT_KEY_PROPERTY = "account.key"; 067 068 public static final String CONTAINER_PROPERTY = "container"; 069 070 protected CloudStorageAccount storageAccount; 071 072 protected CloudBlobClient blobClient; 073 074 protected CloudBlobContainer container; 075 076 @Override 077 protected String getPropertyPrefix() { 078 return PROPERTIES_PREFIX; 079 } 080 081 @Override 082 protected void setupCloudClient() throws IOException { 083 if (StringUtils.isBlank(properties.get(AzureBinaryManager.ACCOUNT_KEY_PROPERTY))) { 084 properties.put(AzureBinaryManager.ACCOUNT_NAME_PROPERTY, System.getenv("AZURE_STORAGE_ACCOUNT")); 085 properties.put(AzureBinaryManager.ACCOUNT_KEY_PROPERTY, System.getenv("AZURE_STORAGE_ACCESS_KEY")); 086 } 087 088 String connectionString = String.format(STORAGE_CONNECTION_STRING, 089 getProperty(ENDPOINT_PROTOCOL_PROPERTY, "https"), getProperty(ACCOUNT_NAME_PROPERTY), 090 getProperty(ACCOUNT_KEY_PROPERTY)); 091 try { 092 storageAccount = CloudStorageAccount.parse(connectionString); 093 094 blobClient = storageAccount.createCloudBlobClient(); 095 container = blobClient.getContainerReference(getProperty(CONTAINER_PROPERTY)); 096 container.createIfNotExists(); 097 } catch (URISyntaxException | InvalidKeyException | StorageException e) { 098 throw new IOException("Unable to initialize Azure binary manager", e); 099 } 100 } 101 102 protected BinaryGarbageCollector instantiateGarbageCollector() { 103 return new AzureGarbageCollector(this); 104 } 105 106 protected FileStorage getFileStorage() { 107 return new AzureFileStorage(container); 108 } 109 110 @Override 111 protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException { 112 try { 113 CloudBlockBlob blockBlobReference = container.getBlockBlobReference(digest); 114 SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); 115 policy.setPermissionsFromString("r"); 116 117 Instant endDateTime = LocalDateTime.now() 118 .plusSeconds(directDownloadExpire) 119 .atZone(ZoneId.systemDefault()) 120 .toInstant(); 121 policy.setSharedAccessExpiryTime(Date.from(endDateTime)); 122 123 SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders(); 124 headers.setContentDisposition(getContentDispositionHeader(blob, servletRequest)); 125 headers.setContentType(getContentTypeHeader(blob)); 126 127 String sas = blockBlobReference.generateSharedAccessSignature(policy, headers, null); 128 129 CloudBlockBlob signedBlob = new CloudBlockBlob(blockBlobReference.getUri(), 130 new StorageCredentialsSharedAccessSignature(sas)); 131 return signedBlob.getQualifiedUri(); 132 } catch (URISyntaxException | InvalidKeyException | StorageException e) { 133 throw new IOException(e); 134 } 135 } 136 137 protected void removeBinary(String digest) { 138 try { 139 container.getBlockBlobReference(digest).delete(); 140 } catch (StorageException | URISyntaxException e) { 141 log.error("Unable to remove binary " + digest, e); 142 } 143 } 144 145 @Override 146 public void removeBinaries(Collection<String> digests) { 147 digests.forEach(this::removeBinary); 148 } 149}