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 BinaryManagerRootDescriptor descriptor = new BinaryManagerRootDescriptor(); 060 descriptor.digest = getDefaultDigestAlgorithm(); 061 setDescriptor(descriptor); 062 log.info("Registering binary manager '" + blobProviderId + "' using " + getClass().getSimpleName()); 063 } 064 065 /** 066 * Initialize the cache. 067 * 068 * @param dir the directory to use to store cached files 069 * @param maxSize the maximum size of the cache (in bytes) 070 * @param maxCount the maximum number of files in the cache 071 * @param minAge the minimum age of a file in the cache to be eligible for removal (in seconds) 072 * @param fileStorage the file storage mechanism to use to store and fetch files 073 * @since 5.9.2 074 */ 075 protected void initializeCache(File dir, long maxSize, long maxCount, long minAge, FileStorage fileStorage) { 076 fileCache = new LRUFileCache(dir, maxSize, maxCount, minAge); 077 this.fileStorage = fileStorage; 078 } 079 080 /** 081 * Initialize the cache. 082 * 083 * @param maxSizeStr the maximum size of the cache (as a String) 084 * @param fileStorage the file storage mechanism to use to store and fetch files 085 * @see #initializeCache(String, String, String, FileStorage) 086 * @since 6.0 087 */ 088 public void initializeCache(String maxSizeStr, FileStorage fileStorage) throws IOException { 089 String maxCountStr = "10000"; // default for legacy code 090 String minAgeStr = "3600"; // default for legacy code 091 initializeCache(maxSizeStr, maxCountStr, minAgeStr, fileStorage); 092 } 093 094 /** 095 * Initializes the cache. 096 * 097 * @param maxSizeStr the maximum size of the cache (as a String) 098 * @param maxCountStr the maximum number of files in the cache 099 * @param minAgeStr the minimum age of a file in the cache to be eligible for removal (in seconds) 100 * @param fileStorage the file storage mechanism to use to store and fetch files 101 * @see SizeUtils#parseSizeInBytes(String) 102 * @since 7.10-HF03, 8.1 103 */ 104 public void initializeCache(String maxSizeStr, String maxCountStr, String minAgeStr, FileStorage fileStorage) 105 throws IOException { 106 cachedir = Framework.createTempFile("nxbincache.", ""); 107 cachedir.delete(); 108 cachedir.mkdir(); 109 long maxSize = SizeUtils.parseSizeInBytes(maxSizeStr); 110 long maxCount = Long.parseLong(maxCountStr); 111 long minAge = Long.parseLong(minAgeStr); 112 initializeCache(cachedir, maxSize, maxCount, minAge, fileStorage); 113 log.info("Using binary cache directory: " + cachedir.getPath() + " size: " + maxSizeStr + " maxCount: " 114 + maxCount + " minAge: " + minAge); 115 116 // be sure FileTracker won't steal our files ! 117 FileEventTracker.registerProtectedPath(cachedir.getAbsolutePath()); 118 } 119 120 @Override 121 public void close() { 122 fileCache.clear(); 123 if (cachedir != null) { 124 try { 125 FileUtils.deleteDirectory(cachedir); 126 } catch (IOException e) { 127 throw new NuxeoException(e); 128 } 129 } 130 } 131 132 @Override 133 protected Binary getBinary(InputStream in) throws IOException { 134 // write the input stream to a temporary file, while computing a digest 135 File tmp = fileCache.getTempFile(); 136 OutputStream out = new FileOutputStream(tmp); 137 String digest; 138 try { 139 digest = storeAndDigest(in, out); 140 } finally { 141 in.close(); 142 out.close(); 143 } 144 145 File cachedFile = fileCache.getFile(digest); 146 if (cachedFile != null) { 147 // file already in cache 148 if (Framework.isTestModeSet()) { 149 Framework.getProperties().setProperty("cachedBinary", digest); 150 } 151 // delete tmp file, not needed anymore 152 tmp.delete(); 153 } else { 154 // send the file to storage 155 fileStorage.storeFile(digest, tmp); 156 // register the file in the file cache 157 fileCache.putFile(digest, tmp); 158 } 159 return getBinary(digest); 160 } 161 162 @Override 163 public Binary getBinary(String digest) { 164 return new LazyBinary(digest, blobProviderId, this); 165 } 166 167 /* =============== Methods used by LazyBinary =============== */ 168 169 /** 170 * Gets a file from cache or storage. 171 * <p> 172 * Used by {@link LazyBinary}. 173 */ 174 public File getFile(String digest) throws IOException { 175 // get file from cache 176 File file = fileCache.getFile(digest); 177 if (file != null) { 178 return file; 179 } 180 // fetch file from storage 181 File tmp = fileCache.getTempFile(); 182 if (fileStorage.fetchFile(digest, tmp)) { 183 // put file in cache 184 file = fileCache.putFile(digest, tmp); 185 return file; 186 } else { 187 // file not in storage 188 tmp.delete(); 189 return null; 190 } 191 } 192 193}