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