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