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