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}