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.isBlank;
023import static org.apache.commons.lang3.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.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 Map<String, String> properties;
077
078    protected boolean directDownload = false;
079
080    protected int directDownloadExpire;
081
082    protected boolean transientFlag;
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    @Override
105    public void initialize(String blobProviderId, Map<String, String> properties) throws IOException {
106        super.initialize(blobProviderId, properties);
107        this.properties = properties;
108
109        // Enable direct download from the remote binary store
110        directDownload = Boolean.parseBoolean(getProperty(DIRECTDOWNLOAD_PROPERTY, DEFAULT_DIRECTDOWNLOAD));
111        directDownloadExpire = getIntProperty(DIRECTDOWNLOAD_EXPIRE_PROPERTY);
112        if (directDownloadExpire < 0) {
113            directDownloadExpire = DEFAULT_DIRECTDOWNLOAD_EXPIRE;
114        }
115
116        transientFlag = Boolean.parseBoolean(getProperty(BlobProviderDescriptor.TRANSIENT));
117
118        // Setup remote client
119        setupCloudClient();
120
121        // Set cache size
122        String cacheSizeStr = getProperty(CACHE_SIZE_PROPERTY, DEFAULT_CACHE_SIZE);
123        String cacheCountStr = getProperty(CACHE_COUNT_PROPERTY, DEFAULT_CACHE_COUNT);
124        String minAgeStr = getProperty(CACHE_MIN_AGE_PROPERTY, DEFAULT_CACHE_MIN_AGE);
125        initializeCache(cacheSizeStr, cacheCountStr, minAgeStr, getFileStorage());
126
127        garbageCollector = instantiateGarbageCollector();
128    }
129
130    @Override
131    public BinaryManager getBinaryManager() {
132        return this;
133    }
134
135    @Override
136    public Blob readBlob(BlobInfo blobInfo) throws IOException {
137        // just delegate to avoid copy/pasting code
138        return new BinaryBlobProvider(this).readBlob(blobInfo);
139    }
140
141    @Override
142    public String writeBlob(Blob blob) throws IOException {
143        // just delegate to avoid copy/pasting code
144        return new BinaryBlobProvider(this).writeBlob(blob);
145    }
146
147    @Override
148    public boolean performsExternalAccessControl(BlobInfo blobInfo) {
149        return new BinaryBlobProvider(this).performsExternalAccessControl(blobInfo);
150    }
151
152    @Override
153    public boolean supportsUserUpdate() {
154        return supportsUserUpdateDefaultTrue();
155    }
156
157    protected boolean supportsUserUpdateDefaultTrue() {
158        return !Boolean.parseBoolean(properties.get(PREVENT_USER_UPDATE));
159    }
160
161    @Override
162    public URI getURI(ManagedBlob blob, BlobManager.UsageHint hint, HttpServletRequest servletRequest)
163            throws IOException {
164        if (hint != BlobManager.UsageHint.DOWNLOAD || !isDirectDownload()) {
165            return null;
166        }
167        String digest = blob.getKey();
168        // strip prefix
169        int colon = digest.indexOf(':');
170        if (colon >= 0) {
171            digest = digest.substring(colon + 1);
172        }
173
174        return getRemoteUri(digest, blob, servletRequest);
175    }
176
177    protected boolean isDirectDownload() {
178        return directDownload;
179    }
180
181    protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException {
182        throw new UnsupportedOperationException();
183    }
184
185    protected String getProperty(String propertyName) {
186        return getProperty(propertyName, null);
187    }
188
189    protected String getProperty(String propertyName, String defaultValue) {
190        String propValue = properties.get(propertyName);
191        if (isNotBlank(propValue)) {
192            return propValue;
193        }
194        propValue = Framework.getProperty(getSystemPropertyName(propertyName));
195        if (isNotBlank(propValue)) {
196            return propValue;
197        }
198        return defaultValue;
199    }
200
201    /**
202     * Gets an integer property, or -1 if undefined.
203     */
204    protected int getIntProperty(String key) {
205        String s = getProperty(key);
206        int value = -1;
207        if (!isBlank(s)) {
208            try {
209                value = Integer.parseInt(s.trim());
210            } catch (NumberFormatException e) {
211                log.error("Cannot parse " + key + ": " + s);
212            }
213        }
214        return value;
215    }
216
217    public String getSystemPropertyName(String propertyName) {
218        return getSystemPropertyPrefix() + "." + propertyName;
219    }
220
221    protected String getContentTypeHeader(Blob blob) {
222        String contentType = blob.getMimeType();
223        String encoding = blob.getEncoding();
224        if (contentType != null && !StringUtils.isBlank(encoding)) {
225            int i = contentType.indexOf(';');
226            if (i >= 0) {
227                contentType = contentType.substring(0, i);
228            }
229            contentType += "; charset=" + encoding;
230        }
231        return contentType;
232    }
233
234    protected String getContentDispositionHeader(Blob blob, HttpServletRequest servletRequest) {
235        if (servletRequest == null) {
236            return RFC2231.encodeContentDisposition(blob.getFilename(), false, null);
237        } else {
238            return DownloadHelper.getRFC2231ContentDisposition(servletRequest, blob.getFilename());
239        }
240    }
241
242    @Override
243    public boolean isTransient() {
244        return transientFlag;
245    }
246
247    @Override
248    public Map<String, String> getProperties() {
249        return properties;
250    }
251}