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