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;
021
022import static org.apache.commons.lang.StringUtils.isBlank;
023import static org.apache.commons.lang.StringUtils.isNotBlank;
024import static org.nuxeo.ecm.core.blob.BlobProviderDescriptor.PREVENT_USER_UPDATE;
025
026import java.io.IOException;
027import java.net.URI;
028import java.util.Collection;
029import java.util.Map;
030
031import javax.servlet.http.HttpServletRequest;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.common.utils.RFC2231;
037import org.nuxeo.ecm.core.api.Blob;
038import org.nuxeo.ecm.core.blob.BlobInfo;
039import org.nuxeo.ecm.core.blob.BlobManager;
040import org.nuxeo.ecm.core.blob.BlobProvider;
041import org.nuxeo.ecm.core.blob.ManagedBlob;
042import org.nuxeo.ecm.core.blob.binary.BinaryBlobProvider;
043import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector;
044import org.nuxeo.ecm.core.blob.binary.BinaryManager;
045import org.nuxeo.ecm.core.blob.binary.CachingBinaryManager;
046import org.nuxeo.ecm.core.blob.binary.FileStorage;
047import org.nuxeo.ecm.core.io.download.DownloadHelper;
048import org.nuxeo.runtime.api.Framework;
049
050/**
051 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a>
052 * @since 7.10
053 */
054public abstract class AbstractCloudBinaryManager extends CachingBinaryManager implements BlobProvider {
055
056    private static final Log log = LogFactory.getLog(AbstractCloudBinaryManager.class);
057
058    /**
059     * Gets the prefix used for configuration using system properties.
060     */
061    protected abstract String getSystemPropertyPrefix();
062
063    protected abstract FileStorage getFileStorage();
064
065    protected abstract BinaryGarbageCollector instantiateGarbageCollector();
066
067    @Override
068    public abstract void removeBinaries(Collection<String> digests);
069
070    /**
071     * Configure Cloud client using properties
072     */
073    protected abstract void setupCloudClient() throws IOException;
074
075    protected Map<String, String> properties;
076
077    protected boolean directDownload = false;
078
079    protected int directDownloadExpire;
080
081    public static final String CACHE_SIZE_PROPERTY = "cachesize";
082
083    public static final String CACHE_COUNT_PROPERTY = "cachecount";
084
085    public static final String CACHE_MIN_AGE_PROPERTY = "cacheminage";
086
087    public static final String DEFAULT_CACHE_SIZE = "100 mb";
088
089    public static final String DEFAULT_CACHE_COUNT = "10000";
090
091    public static final String DEFAULT_CACHE_MIN_AGE = "3600"; // 1h
092
093    public static final String DIRECTDOWNLOAD_PROPERTY = "directdownload";
094
095    public static final String DEFAULT_DIRECTDOWNLOAD = "false";
096
097    public static final String DIRECTDOWNLOAD_EXPIRE_PROPERTY = "directdownload.expire";
098
099    public static final int DEFAULT_DIRECTDOWNLOAD_EXPIRE = 60 * 60; // 1h
100
101    @Override
102    public void initialize(String blobProviderId, Map<String, String> properties) throws IOException {
103        super.initialize(blobProviderId, properties);
104        this.properties = properties;
105
106        // Enable direct download from the remote binary store
107        directDownload = Boolean.parseBoolean(getProperty(DIRECTDOWNLOAD_PROPERTY, DEFAULT_DIRECTDOWNLOAD));
108        directDownloadExpire = getIntProperty(DIRECTDOWNLOAD_EXPIRE_PROPERTY);
109        if (directDownloadExpire < 0) {
110            directDownloadExpire = DEFAULT_DIRECTDOWNLOAD_EXPIRE;
111        }
112
113        // Setup remote client
114        setupCloudClient();
115
116        // Set cache size
117        String cacheSizeStr = getProperty(CACHE_SIZE_PROPERTY, DEFAULT_CACHE_SIZE);
118        String cacheCountStr = getProperty(CACHE_COUNT_PROPERTY, DEFAULT_CACHE_COUNT);
119        String minAgeStr = getProperty(CACHE_MIN_AGE_PROPERTY, DEFAULT_CACHE_MIN_AGE);
120        initializeCache(cacheSizeStr, cacheCountStr, minAgeStr, getFileStorage());
121
122        garbageCollector = instantiateGarbageCollector();
123    }
124
125    @Override
126    public BinaryManager getBinaryManager() {
127        return this;
128    }
129
130    @Override
131    public Blob readBlob(BlobInfo blobInfo) throws IOException {
132        // just delegate to avoid copy/pasting code
133        return new BinaryBlobProvider(this).readBlob(blobInfo);
134    }
135
136    @Override
137    public String writeBlob(Blob blob) throws IOException {
138        // just delegate to avoid copy/pasting code
139        return new BinaryBlobProvider(this).writeBlob(blob);
140    }
141
142    @Override
143    public boolean performsExternalAccessControl(BlobInfo blobInfo) {
144        return new BinaryBlobProvider(this).performsExternalAccessControl(blobInfo);
145    }
146
147    @Override
148    public boolean supportsUserUpdate() {
149        return supportsUserUpdateDefaultTrue();
150    }
151
152    protected boolean supportsUserUpdateDefaultTrue() {
153        return !Boolean.parseBoolean(properties.get(PREVENT_USER_UPDATE));
154    }
155
156    @Override
157    public URI getURI(ManagedBlob blob, BlobManager.UsageHint hint, HttpServletRequest servletRequest)
158            throws IOException {
159        if (hint != BlobManager.UsageHint.DOWNLOAD || !isDirectDownload()) {
160            return null;
161        }
162        String digest = blob.getKey();
163        // strip prefix
164        int colon = digest.indexOf(':');
165        if (colon >= 0) {
166            digest = digest.substring(colon + 1);
167        }
168
169        return getRemoteUri(digest, blob, servletRequest);
170    }
171
172    protected boolean isDirectDownload() {
173        return directDownload;
174    }
175
176    protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException {
177        throw new UnsupportedOperationException();
178    }
179
180    protected String getProperty(String propertyName) {
181        return getProperty(propertyName, null);
182    }
183
184    protected String getProperty(String propertyName, String defaultValue) {
185        String propValue = properties.get(propertyName);
186        if (isNotBlank(propValue)) {
187            return propValue;
188        }
189        propValue = Framework.getProperty(getSystemPropertyName(propertyName));
190        if (isNotBlank(propValue)) {
191            return propValue;
192        }
193        return defaultValue;
194    }
195
196    /**
197     * Gets an integer property, or -1 if undefined.
198     */
199    protected int getIntProperty(String key) {
200        String s = getProperty(key);
201        int value = -1;
202        if (!isBlank(s)) {
203            try {
204                value = Integer.parseInt(s.trim());
205            } catch (NumberFormatException e) {
206                log.error("Cannot parse " + key + ": " + s);
207            }
208        }
209        return value;
210    }
211
212    public String getSystemPropertyName(String propertyName) {
213        return getSystemPropertyPrefix() + "." + propertyName;
214    }
215
216    protected String getContentTypeHeader(Blob blob) {
217        String contentType = blob.getMimeType();
218        String encoding = blob.getEncoding();
219        if (contentType != null && !StringUtils.isBlank(encoding)) {
220            int i = contentType.indexOf(';');
221            if (i >= 0) {
222                contentType = contentType.substring(0, i);
223            }
224            contentType += "; charset=" + encoding;
225        }
226        return contentType;
227    }
228
229    protected String getContentDispositionHeader(Blob blob, HttpServletRequest servletRequest) {
230        if (servletRequest == null) {
231            return RFC2231.encodeContentDisposition(blob.getFilename(), false, null);
232        } else {
233            return DownloadHelper.getRFC2231ContentDisposition(servletRequest, blob.getFilename());
234        }
235    }
236}