001/* 002 * (C) Copyright 2006-2014 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 - initial API and implementation 018 * 019 */ 020package org.nuxeo.ecm.core.convert.cache; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Set; 031import java.util.concurrent.atomic.AtomicLong; 032import java.util.concurrent.locks.ReentrantReadWriteLock; 033 034import org.apache.commons.codec.binary.Base64; 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.nuxeo.common.utils.Path; 038import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 039import org.nuxeo.ecm.core.convert.api.ConversionService; 040import org.nuxeo.ecm.core.convert.service.ConversionServiceImpl; 041 042/** 043 * Manager for the cache system of the {@link ConversionService}. 044 * 045 * @author tiry 046 */ 047public class ConversionCacheHolder { 048 049 protected static final Map<String, ConversionCacheEntry> cache = new HashMap<>(); 050 051 protected static final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); 052 053 private static final Log log = LogFactory.getLog(ConversionCacheHolder.class); 054 055 public static final int NB_SUB_PATH_PART = 5; 056 057 public static final int SUB_PATH_PART_SIZE = 2; 058 059 public static final AtomicLong CACHE_HITS = new AtomicLong(); 060 061 // Utility class. 062 private ConversionCacheHolder() { 063 } 064 065 public static long getCacheHits() { 066 return CACHE_HITS.get(); 067 } 068 069 public static int getNbCacheEntries() { 070 return cache.keySet().size(); 071 } 072 073 protected static List<String> getSubPathFromKey(String key) { 074 List<String> subPath = new ArrayList<>(); 075 076 String path = Base64.encodeBase64String(key.getBytes()); 077 078 path = path.replace("+", "X"); 079 path = path.replace("/", "Y"); 080 081 int idx = 0; 082 083 for (int i = 0; i < NB_SUB_PATH_PART; i++) { 084 String subPart = path.substring(idx, idx + SUB_PATH_PART_SIZE); 085 subPath.add(subPart); 086 idx += SUB_PATH_PART_SIZE; 087 if (idx >= path.length()) { 088 break; 089 } 090 } 091 return subPath; 092 } 093 094 protected static String getCacheEntryPath(String key) { 095 Path path = new Path(ConversionServiceImpl.getCacheBasePath()); 096 097 List<String> subPath = getSubPathFromKey(key); 098 099 for (String subPart : subPath) { 100 path = path.append(subPart); 101 new File(path.toString()).mkdir(); 102 } 103 104 // path = path.append(key); 105 106 return path.toString(); 107 } 108 109 public static void addToCache(String key, BlobHolder result) { 110 Objects.requireNonNull(key); 111 cacheLock.writeLock().lock(); 112 try { 113 doAddToCache(key, result); 114 } finally { 115 cacheLock.writeLock().unlock(); 116 } 117 } 118 119 protected static void doAddToCache(String key, BlobHolder result) { 120 ConversionCacheEntry cce = new ConversionCacheEntry(result); 121 boolean persisted = false; 122 123 try { 124 persisted = cce.persist(getCacheEntryPath(key)); 125 } catch (IOException e) { 126 log.error("Error while trying to persist cache entry", e); 127 } 128 129 if (persisted) { 130 cache.put(key, cce); 131 } 132 } 133 134 public static void removeFromCache(String key) { 135 cacheLock.writeLock().lock(); 136 try { 137 doRemoveFromCache(key); 138 } finally { 139 cacheLock.writeLock().unlock(); 140 } 141 142 } 143 144 protected static void doRemoveFromCache(String key) { 145 if (cache.containsKey(key)) { 146 ConversionCacheEntry cce = cache.get(key); 147 cce.remove(); 148 cache.remove(key); 149 } 150 } 151 152 public static ConversionCacheEntry getCacheEntry(String key) { 153 cacheLock.readLock().lock(); 154 try { 155 return doGetCacheEntry(key); 156 } finally { 157 cacheLock.readLock().unlock(); 158 } 159 } 160 161 protected static ConversionCacheEntry doGetCacheEntry(String key) { 162 return cache.get(key); 163 } 164 165 public static BlobHolder getFromCache(String key) { 166 cacheLock.readLock().lock(); 167 try { 168 return doGetFromCache(key); 169 } finally { 170 cacheLock.readLock().unlock(); 171 } 172 } 173 174 protected static BlobHolder doGetFromCache(String key) { 175 ConversionCacheEntry cacheEntry = cache.get(key); 176 if (cacheEntry != null) { 177 if (CACHE_HITS.incrementAndGet() < 0) { 178 // skip all negative values 179 CACHE_HITS.addAndGet(Long.MIN_VALUE); // back to 0 180 } 181 return cacheEntry.restore(); 182 } 183 return null; 184 } 185 186 public static Set<String> getCacheKeys() { 187 cacheLock.readLock().lock(); 188 try { 189 return new HashSet<>(cache.keySet()); 190 } finally { 191 cacheLock.readLock().unlock(); 192 } 193 } 194 195 /** 196 * @since 6.0 197 */ 198 public static void deleteCache() { 199 cacheLock.writeLock().lock(); 200 try { 201 cache.clear(); 202 new File(ConversionServiceImpl.getCacheBasePath()).delete(); 203 } finally { 204 cacheLock.writeLock().unlock(); 205 } 206 } 207}