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.BlobProvider; 040import org.nuxeo.ecm.core.blob.ManagedBlob; 041import org.nuxeo.ecm.core.blob.binary.BinaryBlobProvider; 042import org.nuxeo.ecm.core.blob.binary.BinaryGarbageCollector; 043import org.nuxeo.ecm.core.blob.binary.BinaryManager; 044import org.nuxeo.ecm.core.blob.binary.CachingBinaryManager; 045import org.nuxeo.ecm.core.blob.binary.FileStorage; 046import org.nuxeo.ecm.core.io.download.DownloadHelper; 047import org.nuxeo.ecm.core.model.Document; 048import org.nuxeo.runtime.api.Framework; 049 050/** 051 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 052 * @since 7.10 053 */ 054public abstract class AbstractCloudBinaryManager extends CachingBinaryManager implements BlobProvider { 055 056 private static final Log log = LogFactory.getLog(AbstractCloudBinaryManager.class); 057 058 /** 059 * Gets the prefix used for configuration using system properties. 060 */ 061 protected abstract String getSystemPropertyPrefix(); 062 063 protected abstract FileStorage getFileStorage(); 064 065 protected abstract BinaryGarbageCollector instantiateGarbageCollector(); 066 067 @Override 068 public abstract void removeBinaries(Collection<String> digests); 069 070 /** 071 * Configure Cloud client using properties 072 */ 073 protected abstract void setupCloudClient() throws IOException; 074 075 protected Map<String, String> properties; 076 077 protected boolean directDownload = false; 078 079 protected int directDownloadExpire; 080 081 public static final String CACHE_SIZE_PROPERTY = "cachesize"; 082 083 public static final String CACHE_COUNT_PROPERTY = "cachecount"; 084 085 public static final String CACHE_MIN_AGE_PROPERTY = "cacheminage"; 086 087 public static final String DEFAULT_CACHE_SIZE = "100 mb"; 088 089 public static final String DEFAULT_CACHE_COUNT = "10000"; 090 091 public static final String DEFAULT_CACHE_MIN_AGE = "3600"; // 1h 092 093 public static final String DIRECTDOWNLOAD_PROPERTY = "directdownload"; 094 095 public static final String DEFAULT_DIRECTDOWNLOAD = "false"; 096 097 public static final String DIRECTDOWNLOAD_EXPIRE_PROPERTY = "directdownload.expire"; 098 099 public static final int DEFAULT_DIRECTDOWNLOAD_EXPIRE = 60 * 60; // 1h 100 101 @Override 102 public void initialize(String blobProviderId, Map<String, String> properties) throws IOException { 103 super.initialize(blobProviderId, properties); 104 this.properties = properties; 105 106 // Enable direct download from the remote binary store 107 directDownload = Boolean.parseBoolean(getProperty(DIRECTDOWNLOAD_PROPERTY, DEFAULT_DIRECTDOWNLOAD)); 108 directDownloadExpire = getIntProperty(DIRECTDOWNLOAD_EXPIRE_PROPERTY); 109 if (directDownloadExpire < 0) { 110 directDownloadExpire = DEFAULT_DIRECTDOWNLOAD_EXPIRE; 111 } 112 113 // Setup remote client 114 setupCloudClient(); 115 116 // Set cache size 117 String cacheSizeStr = getProperty(CACHE_SIZE_PROPERTY, DEFAULT_CACHE_SIZE); 118 String cacheCountStr = getProperty(CACHE_COUNT_PROPERTY, DEFAULT_CACHE_COUNT); 119 String minAgeStr = getProperty(CACHE_MIN_AGE_PROPERTY, DEFAULT_CACHE_MIN_AGE); 120 initializeCache(cacheSizeStr, cacheCountStr, minAgeStr, getFileStorage()); 121 122 garbageCollector = instantiateGarbageCollector(); 123 } 124 125 @Override 126 public BinaryManager getBinaryManager() { 127 return this; 128 } 129 130 @Override 131 public Blob readBlob(BlobManager.BlobInfo blobInfo) throws IOException { 132 // just delegate to avoid copy/pasting code 133 return new BinaryBlobProvider(this).readBlob(blobInfo); 134 } 135 136 @Override 137 public String writeBlob(Blob blob, Document doc) throws IOException { 138 // just delegate to avoid copy/pasting code 139 return new BinaryBlobProvider(this).writeBlob(blob, doc); 140 } 141 142 @Override 143 public boolean supportsUserUpdate() { 144 return supportsUserUpdateDefaultTrue(); 145 } 146 147 protected boolean supportsUserUpdateDefaultTrue() { 148 return !Boolean.parseBoolean(properties.get(PREVENT_USER_UPDATE)); 149 } 150 151 @Override 152 public URI getURI(ManagedBlob blob, BlobManager.UsageHint hint, HttpServletRequest servletRequest) 153 throws IOException { 154 if (hint != BlobManager.UsageHint.DOWNLOAD || !isDirectDownload()) { 155 return null; 156 } 157 String digest = blob.getKey(); 158 // strip prefix 159 int colon = digest.indexOf(':'); 160 if (colon >= 0) { 161 digest = digest.substring(colon + 1); 162 } 163 164 return getRemoteUri(digest, blob, servletRequest); 165 } 166 167 protected boolean isDirectDownload() { 168 return directDownload; 169 } 170 171 protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException { 172 throw new UnsupportedOperationException(); 173 } 174 175 protected String getProperty(String propertyName) { 176 return getProperty(propertyName, null); 177 } 178 179 protected String getProperty(String propertyName, String defaultValue) { 180 String propValue = properties.get(propertyName); 181 if (isNotBlank(propValue)) { 182 return propValue; 183 } 184 propValue = Framework.getProperty(getSystemPropertyName(propertyName)); 185 if (isNotBlank(propValue)) { 186 return propValue; 187 } 188 return defaultValue; 189 } 190 191 /** 192 * Gets an integer property, or -1 if undefined. 193 */ 194 protected int getIntProperty(String key) { 195 String s = getProperty(key); 196 int value = -1; 197 if (!isBlank(s)) { 198 try { 199 value = Integer.parseInt(s.trim()); 200 } catch (NumberFormatException e) { 201 log.error("Cannot parse " + key + ": " + s); 202 } 203 } 204 return value; 205 } 206 207 public String getSystemPropertyName(String propertyName) { 208 return getSystemPropertyPrefix() + "." + propertyName; 209 } 210 211 protected String getContentTypeHeader(Blob blob) { 212 String contentType = blob.getMimeType(); 213 String encoding = blob.getEncoding(); 214 if (contentType != null && !StringUtils.isBlank(encoding)) { 215 int i = contentType.indexOf(';'); 216 if (i >= 0) { 217 contentType = contentType.substring(0, i); 218 } 219 contentType += "; charset=" + encoding; 220 } 221 return contentType; 222 } 223 224 protected String getContentDispositionHeader(Blob blob, HttpServletRequest servletRequest) { 225 if (servletRequest == null) { 226 return RFC2231.encodeContentDisposition(blob.getFilename(), false, null); 227 } else { 228 return DownloadHelper.getRFC2231ContentDisposition(servletRequest, blob.getFilename()); 229 } 230 } 231}