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}