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