001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo 016 */ 017 018package org.nuxeo.ecm.blob; 019 020import static org.apache.commons.lang.StringUtils.isBlank; 021import static org.apache.commons.lang.StringUtils.isNotBlank; 022import static org.nuxeo.ecm.core.blob.BlobProviderDescriptor.PREVENT_USER_UPDATE; 023 024import java.io.IOException; 025import java.net.URI; 026import java.util.Collection; 027import java.util.Map; 028 029import javax.servlet.http.HttpServletRequest; 030 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.common.utils.RFC2231; 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.blob.BlobManager; 037import org.nuxeo.ecm.core.blob.BlobProvider; 038import org.nuxeo.ecm.core.blob.BlobProviderDescriptor; 039import org.nuxeo.ecm.core.blob.ManagedBlob; 040import org.nuxeo.ecm.core.blob.binary.BinaryBlobProvider; 041import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector; 042import org.nuxeo.ecm.core.blob.binary.CachingBinaryManager; 043import org.nuxeo.ecm.core.blob.binary.FileStorage; 044import org.nuxeo.ecm.core.io.download.DownloadHelper; 045import org.nuxeo.ecm.core.model.Document; 046import org.nuxeo.runtime.api.Framework; 047 048/** 049 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 050 * @since 7.10 051 */ 052public abstract class AbstractCloudBinaryManager extends CachingBinaryManager implements BlobProvider { 053 054 private static final Log log = LogFactory.getLog(AbstractCloudBinaryManager.class); 055 056 /** 057 * Gets the property prefix used in configurarion properties 058 */ 059 protected abstract String getPropertyPrefix(); 060 061 protected abstract FileStorage getFileStorage(); 062 063 protected abstract BinaryGarbageCollector instantiateGarbageCollector(); 064 065 @Override 066 public abstract void removeBinaries(Collection<String> digests); 067 068 /** 069 * Configure Cloud client using properties 070 */ 071 protected abstract void setupCloudClient() throws IOException; 072 073 protected Map<String, String> properties; 074 075 protected boolean directDownload = false; 076 077 protected int directDownloadExpire; 078 079 public static final String CACHE_PROPERTY = "cache"; 080 081 public static final String DEFAULT_CACHE_SIZE = "100 mb"; 082 083 public static final String DIRECTDOWNLOAD_PROPERTY = "directdownload"; 084 085 public static final String DEFAULT_DIRECTDOWNLOAD = "false"; 086 087 public static final String DIRECTDOWNLOAD_EXPIRE_PROPERTY = "directdownload.expire"; 088 089 public static final int DEFAULT_DIRECTDOWNLOAD_EXPIRE = 60 * 60; // 1h 090 091 @Override 092 public void initialize(String blobProviderId, Map<String, String> properties) throws IOException { 093 super.initialize(blobProviderId, properties); 094 this.properties = properties; 095 096 // Enable direct download from the remote binary store 097 directDownload = Boolean.parseBoolean(getProperty(DIRECTDOWNLOAD_PROPERTY, DEFAULT_DIRECTDOWNLOAD)); 098 directDownloadExpire = getIntFrameworkProperty(DIRECTDOWNLOAD_EXPIRE_PROPERTY); 099 if (directDownloadExpire < 0) { 100 directDownloadExpire = DEFAULT_DIRECTDOWNLOAD_EXPIRE; 101 } 102 103 // Setup remote client 104 setupCloudClient(); 105 106 // Set cache size 107 String cacheSizeStr = getProperty(CACHE_PROPERTY, DEFAULT_CACHE_SIZE); 108 initializeCache(cacheSizeStr, getFileStorage()); 109 110 garbageCollector = instantiateGarbageCollector(); 111 } 112 113 @Override 114 public Blob readBlob(BlobManager.BlobInfo blobInfo) throws IOException { 115 // just delegate to avoid copy/pasting code 116 return new BinaryBlobProvider(this).readBlob(blobInfo); 117 } 118 119 @Override 120 public String writeBlob(Blob blob, Document doc) throws IOException { 121 // just delegate to avoid copy/pasting code 122 return new BinaryBlobProvider(this).writeBlob(blob, doc); 123 } 124 125 @Override 126 public boolean supportsUserUpdate() { 127 return supportsUserUpdateDefaultTrue(); 128 } 129 130 protected boolean supportsUserUpdateDefaultTrue() { 131 return !Boolean.parseBoolean(properties.get(PREVENT_USER_UPDATE)); 132 } 133 134 @Override 135 public URI getURI(ManagedBlob blob, BlobManager.UsageHint hint, HttpServletRequest servletRequest) 136 throws IOException { 137 if (hint != BlobManager.UsageHint.DOWNLOAD || !isDirectDownload()) { 138 return null; 139 } 140 String digest = blob.getKey(); 141 // strip prefix 142 int colon = digest.indexOf(':'); 143 if (colon >= 0) { 144 digest = digest.substring(colon + 1); 145 } 146 147 return getRemoteUri(digest, blob, servletRequest); 148 } 149 150 protected boolean isDirectDownload() { 151 return directDownload; 152 } 153 154 protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException { 155 throw new UnsupportedOperationException(); 156 } 157 158 protected String getProperty(String propertyName) { 159 return getProperty(propertyName, null); 160 } 161 162 protected String getProperty(String propertyName, String defaultValue) { 163 String propValue = properties.get(propertyName); 164 if (isNotBlank(propValue)) { 165 return propValue; 166 } 167 168 return Framework.getProperty(getConfigurationKey(propertyName), defaultValue); 169 } 170 171 /** 172 * Gets an integer framework property, or -1 if undefined. 173 */ 174 protected static int getIntFrameworkProperty(String key) { 175 String s = Framework.getProperty(key); 176 int value = -1; 177 if (!isBlank(s)) { 178 try { 179 value = Integer.parseInt(s.trim()); 180 } catch (NumberFormatException e) { 181 log.error("Cannot parse " + key + ": " + s); 182 } 183 } 184 return value; 185 } 186 187 public String getConfigurationKey(String propertyName) { 188 return String.format("%s.%s", getPropertyPrefix(), propertyName); 189 } 190 191 protected String getContentTypeHeader(Blob blob) { 192 String contentType = blob.getMimeType(); 193 String encoding = blob.getEncoding(); 194 if (contentType != null && !StringUtils.isBlank(encoding)) { 195 int i = contentType.indexOf(';'); 196 if (i >= 0) { 197 contentType = contentType.substring(0, i); 198 } 199 contentType += "; charset=" + encoding; 200 } 201 return contentType; 202 } 203 204 protected String getContentDispositionHeader(Blob blob, HttpServletRequest servletRequest) { 205 if (servletRequest == null) { 206 return RFC2231.encodeContentDisposition(blob.getFilename(), false, null); 207 } else { 208 return DownloadHelper.getRFC2231ContentDisposition(servletRequest, blob.getFilename()); 209 } 210 } 211}