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