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}