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}