001/* 002 * (C) Copyright 2011-2016 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 * Stephane Lacoin 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.blob.binary; 021 022import java.io.File; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.util.Map; 028 029import org.apache.commons.io.FileUtils; 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.common.file.FileCache; 033import org.nuxeo.common.file.LRUFileCache; 034import org.nuxeo.common.utils.SizeUtils; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.runtime.api.Framework; 037import org.nuxeo.runtime.trackers.files.FileEventTracker; 038 039/** 040 * Abstract class for a {@link BinaryManager} that uses a cache for its files because fetching them is expensive. 041 * <p> 042 * Initialization of the {@link BinaryManager} must call {@link #initializeCache} from the {@link #initialize} method. 043 * 044 * @since 5.7 045 */ 046public abstract class CachingBinaryManager extends AbstractBinaryManager { 047 048 private static final Log log = LogFactory.getLog(CachingBinaryManager.class); 049 050 protected File cachedir; 051 052 public FileCache fileCache; 053 054 protected FileStorage fileStorage; 055 056 @Override 057 public void initialize(String blobProviderId, Map<String, String> properties) throws IOException { 058 super.initialize(blobProviderId, properties); 059 descriptor = new BinaryManagerRootDescriptor(); 060 descriptor.digest = getDefaultDigestAlgorithm(); 061 log.info("Registering binary manager '" + blobProviderId + "' using " + getClass().getSimpleName()); 062 } 063 064 /** 065 * Initialize the cache. 066 * 067 * @param dir the directory to use to store cached files 068 * @param maxSize the maximum size of the cache (in bytes) 069 * @param maxCount the maximum number of files in the cache 070 * @param minAge the minimum age of a file in the cache to be eligible for removal (in seconds) 071 * @param fileStorage the file storage mechanism to use to store and fetch files 072 * @since 5.9.2 073 */ 074 protected void initializeCache(File dir, long maxSize, long maxCount, long minAge, FileStorage fileStorage) { 075 fileCache = new LRUFileCache(dir, maxSize, maxCount, minAge); 076 this.fileStorage = fileStorage; 077 } 078 079 /** 080 * Initialize the cache. 081 * 082 * @param maxSizeStr the maximum size of the cache (as a String) 083 * @param fileStorage the file storage mechanism to use to store and fetch files 084 * @see #initializeCache(String, String, String, FileStorage) 085 * @since 6.0 086 */ 087 public void initializeCache(String maxSizeStr, FileStorage fileStorage) throws IOException { 088 String maxCountStr = "10000"; // default for legacy code 089 String minAgeStr = "3600"; // default for legacy code 090 initializeCache(maxSizeStr, maxCountStr, minAgeStr, fileStorage); 091 } 092 093 /** 094 * Initializes the cache. 095 * 096 * @param maxSizeStr the maximum size of the cache (as a String) 097 * @param maxCountStr the maximum number of files in the cache 098 * @param minAgeStr the minimum age of a file in the cache to be eligible for removal (in seconds) 099 * @param fileStorage the file storage mechanism to use to store and fetch files 100 * @see SizeUtils#parseSizeInBytes(String) 101 * @since 7.10-HF03, 8.1 102 */ 103 public void initializeCache(String maxSizeStr, String maxCountStr, String minAgeStr, FileStorage fileStorage) 104 throws IOException { 105 cachedir = Framework.createTempFile("nxbincache.", ""); 106 cachedir.delete(); 107 cachedir.mkdir(); 108 long maxSize = SizeUtils.parseSizeInBytes(maxSizeStr); 109 long maxCount = Long.parseLong(maxCountStr); 110 long minAge = Long.parseLong(minAgeStr); 111 initializeCache(cachedir, maxSize, maxCount, minAge, fileStorage); 112 log.info("Using binary cache directory: " + cachedir.getPath() + " size: " + maxSizeStr + " maxCount: " 113 + maxCount + " minAge: " + minAge); 114 115 // be sure FileTracker won't steal our files ! 116 FileEventTracker.registerProtectedPath(cachedir.getAbsolutePath()); 117 } 118 119 @Override 120 public void close() { 121 fileCache.clear(); 122 if (cachedir != null) { 123 try { 124 FileUtils.deleteDirectory(cachedir); 125 } catch (IOException e) { 126 throw new NuxeoException(e); 127 } 128 } 129 } 130 131 @Override 132 protected Binary getBinary(InputStream in) throws IOException { 133 // write the input stream to a temporary file, while computing a digest 134 File tmp = fileCache.getTempFile(); 135 OutputStream out = new FileOutputStream(tmp); 136 String digest; 137 try { 138 digest = storeAndDigest(in, out); 139 } finally { 140 in.close(); 141 out.close(); 142 } 143 144 File cachedFile = fileCache.getFile(digest); 145 if (cachedFile != null) { 146 // file already in cache 147 if (Framework.isTestModeSet()) { 148 Framework.getProperties().setProperty("cachedBinary", digest); 149 } 150 // delete tmp file, not needed anymore 151 tmp.delete(); 152 } else { 153 // send the file to storage 154 fileStorage.storeFile(digest, tmp); 155 // register the file in the file cache 156 fileCache.putFile(digest, tmp); 157 } 158 return getBinary(digest); 159 } 160 161 @Override 162 public Binary getBinary(String digest) { 163 return new LazyBinary(digest, blobProviderId, this); 164 } 165 166 /* =============== Methods used by LazyBinary =============== */ 167 168 /** 169 * Gets a file from cache or storage. 170 * <p> 171 * Used by {@link LazyBinary}. 172 */ 173 public File getFile(String digest) throws IOException { 174 // get file from cache 175 File file = fileCache.getFile(digest); 176 if (file != null) { 177 return file; 178 } 179 // fetch file from storage 180 File tmp = fileCache.getTempFile(); 181 if (fileStorage.fetchFile(digest, tmp)) { 182 // put file in cache 183 file = fileCache.putFile(digest, tmp); 184 return file; 185 } else { 186 // file not in storage 187 tmp.delete(); 188 return null; 189 } 190 } 191 192}