source: josm/trunk/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java@ 13647

Last change on this file since 13647 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.cache;
3
4import java.io.File;
5import java.io.IOException;
6import java.nio.channels.FileChannel;
7import java.nio.channels.FileLock;
8import java.nio.file.StandardOpenOption;
9import java.util.Arrays;
10import java.util.Properties;
11import java.util.logging.Handler;
12import java.util.logging.Level;
13import java.util.logging.LogRecord;
14import java.util.logging.Logger;
15import java.util.logging.SimpleFormatter;
16
17import org.apache.commons.jcs.access.CacheAccess;
18import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
19import org.apache.commons.jcs.auxiliary.AuxiliaryCacheFactory;
20import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes;
21import org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheAttributes;
22import org.apache.commons.jcs.auxiliary.disk.block.BlockDiskCacheFactory;
23import org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes;
24import org.apache.commons.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory;
25import org.apache.commons.jcs.engine.CompositeCacheAttributes;
26import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
27import org.apache.commons.jcs.engine.control.CompositeCache;
28import org.apache.commons.jcs.engine.control.CompositeCacheManager;
29import org.apache.commons.jcs.utils.serialization.StandardSerializer;
30import org.openstreetmap.josm.data.preferences.BooleanProperty;
31import org.openstreetmap.josm.data.preferences.IntegerProperty;
32import org.openstreetmap.josm.spi.preferences.Config;
33import org.openstreetmap.josm.tools.Logging;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * Wrapper class for JCS Cache. Sets some sane environment and returns instances of cache objects.
38 * Static configuration for now assumes some small LRU cache in memory and larger LRU cache on disk
39 *
40 * @author Wiktor Niesiobędzki
41 * @since 8168
42 */
43public final class JCSCacheManager {
44 private static volatile CompositeCacheManager cacheManager;
45 private static final long maxObjectTTL = -1;
46 private static final String PREFERENCE_PREFIX = "jcs.cache";
47 public static final BooleanProperty USE_BLOCK_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_block_cache", true);
48
49 private static final AuxiliaryCacheFactory DISK_CACHE_FACTORY =
50 USE_BLOCK_CACHE.get() ? new BlockDiskCacheFactory() : new IndexedDiskCacheFactory();
51 private static FileLock cacheDirLock;
52
53 /**
54 * default objects to be held in memory by JCS caches (per region)
55 */
56 public static final IntegerProperty DEFAULT_MAX_OBJECTS_IN_MEMORY = new IntegerProperty(PREFERENCE_PREFIX + ".max_objects_in_memory", 1000);
57
58 private static final Logger jcsLog;
59
60 static {
61 // raising logging level gives ~500x performance gain
62 // http://westsworld.dk/blog/2008/01/jcs-and-performance/
63 jcsLog = Logger.getLogger("org.apache.commons.jcs");
64 try {
65 jcsLog.setLevel(Level.INFO);
66 jcsLog.setUseParentHandlers(false);
67 // we need a separate handler from Main's, as we downgrade LEVEL.INFO to DEBUG level
68 Arrays.stream(jcsLog.getHandlers()).forEach(jcsLog::removeHandler);
69 jcsLog.addHandler(new Handler() {
70 final SimpleFormatter formatter = new SimpleFormatter();
71
72 @Override
73 public void publish(LogRecord record) {
74 String msg = formatter.formatMessage(record);
75 if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
76 Logging.error(msg);
77 } else if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
78 Logging.warn(msg);
79 // downgrade INFO level to debug, as JCS is too verbose at INFO level
80 } else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
81 Logging.debug(msg);
82 } else {
83 Logging.trace(msg);
84 }
85 }
86
87 @Override
88 public void flush() {
89 // nothing to be done on flush
90 }
91
92 @Override
93 public void close() {
94 // nothing to be done on close
95 }
96 });
97 } catch (SecurityException e) {
98 Logging.log(Logging.LEVEL_ERROR, "Unable to configure JCS logs", e);
99 }
100 }
101
102 private JCSCacheManager() {
103 // Hide implicit public constructor for utility classes
104 }
105
106 @SuppressWarnings("resource")
107 private static void initialize() {
108 File cacheDir = new File(Config.getDirs().getCacheDirectory(true), "jcs");
109
110 if (!cacheDir.exists() && !cacheDir.mkdirs()) {
111 Logging.warn("Cache directory " + cacheDir.toString() + " does not exists and could not create it");
112 } else {
113 File cacheDirLockPath = new File(cacheDir, ".lock");
114 try {
115 if (!cacheDirLockPath.exists() && !cacheDirLockPath.createNewFile()) {
116 Logging.warn("Cannot create cache dir lock file");
117 }
118 cacheDirLock = FileChannel.open(cacheDirLockPath.toPath(), StandardOpenOption.WRITE).tryLock();
119
120 if (cacheDirLock == null)
121 Logging.warn("Cannot lock cache directory. Will not use disk cache");
122 } catch (IOException e) {
123 Logging.warn("Cannot create cache dir \"" + cacheDirLockPath.toString() + "\" lock file: " + e.toString());
124 Logging.warn("Will not use disk cache");
125 }
126 }
127 // this could be moved to external file
128 Properties props = new Properties();
129 // these are default common to all cache regions
130 // use of auxiliary cache and sizing of the caches is done with giving proper getCache(...) params
131 // CHECKSTYLE.OFF: SingleSpaceSeparator
132 props.setProperty("jcs.default.cacheattributes", CompositeCacheAttributes.class.getCanonicalName());
133 props.setProperty("jcs.default.cacheattributes.MaxObjects", DEFAULT_MAX_OBJECTS_IN_MEMORY.get().toString());
134 props.setProperty("jcs.default.cacheattributes.UseMemoryShrinker", "true");
135 props.setProperty("jcs.default.cacheattributes.DiskUsagePatternName", "UPDATE"); // store elements on disk on put
136 props.setProperty("jcs.default.elementattributes", CacheEntryAttributes.class.getCanonicalName());
137 props.setProperty("jcs.default.elementattributes.IsEternal", "false");
138 props.setProperty("jcs.default.elementattributes.MaxLife", Long.toString(maxObjectTTL));
139 props.setProperty("jcs.default.elementattributes.IdleTime", Long.toString(maxObjectTTL));
140 props.setProperty("jcs.default.elementattributes.IsSpool", "true");
141 // CHECKSTYLE.ON: SingleSpaceSeparator
142 CompositeCacheManager cm = CompositeCacheManager.getUnconfiguredInstance();
143 cm.configure(props);
144 cacheManager = cm;
145 }
146
147 /**
148 * Returns configured cache object for named cache region
149 * @param <K> key type
150 * @param <V> value type
151 * @param cacheName region name
152 * @return cache access object
153 */
154 public static <K, V> CacheAccess<K, V> getCache(String cacheName) {
155 return getCache(cacheName, DEFAULT_MAX_OBJECTS_IN_MEMORY.get().intValue(), 0, null);
156 }
157
158 /**
159 * Returns configured cache object with defined limits of memory cache and disk cache
160 * @param <K> key type
161 * @param <V> value type
162 * @param cacheName region name
163 * @param maxMemoryObjects number of objects to keep in memory
164 * @param maxDiskObjects maximum size of the objects stored on disk in kB
165 * @param cachePath path to disk cache. if null, no disk cache will be created
166 * @return cache access object
167 */
168 public static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects, String cachePath) {
169 if (cacheManager != null)
170 return getCacheInner(cacheName, maxMemoryObjects, maxDiskObjects, cachePath);
171
172 synchronized (JCSCacheManager.class) {
173 if (cacheManager == null)
174 initialize();
175 return getCacheInner(cacheName, maxMemoryObjects, maxDiskObjects, cachePath);
176 }
177 }
178
179 @SuppressWarnings("unchecked")
180 private static <K, V> CacheAccess<K, V> getCacheInner(String cacheName, int maxMemoryObjects, int maxDiskObjects, String cachePath) {
181 CompositeCache<K, V> cc = cacheManager.getCache(cacheName, getCacheAttributes(maxMemoryObjects));
182
183 if (cachePath != null && cacheDirLock != null) {
184 IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName);
185 try {
186 if (cc.getAuxCaches().length == 0) {
187 cc.setAuxCaches(new AuxiliaryCache[]{DISK_CACHE_FACTORY.createCache(
188 diskAttributes, cacheManager, null, new StandardSerializer())});
189 }
190 } catch (Exception e) { // NOPMD
191 // in case any error in setting auxiliary cache, do not use disk cache at all - only memory
192 cc.setAuxCaches(new AuxiliaryCache[0]);
193 }
194 }
195 return new CacheAccess<>(cc);
196 }
197
198 /**
199 * Close all files to ensure, that all indexes and data are properly written
200 */
201 public static void shutdown() {
202 // use volatile semantics to get consistent object
203 CompositeCacheManager localCacheManager = cacheManager;
204 if (localCacheManager != null) {
205 localCacheManager.shutDown();
206 }
207 }
208
209 private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName) {
210 IDiskCacheAttributes ret;
211 removeStaleFiles(cachePath + File.separator + cacheName, USE_BLOCK_CACHE.get() ? "_INDEX_v2" : "_BLOCK_v2");
212 String newCacheName = cacheName + (USE_BLOCK_CACHE.get() ? "_BLOCK_v2" : "_INDEX_v2");
213
214 if (USE_BLOCK_CACHE.get()) {
215 BlockDiskCacheAttributes blockAttr = new BlockDiskCacheAttributes();
216 /*
217 * BlockDiskCache never optimizes the file, so when file size is reduced, it will never be truncated to desired size.
218 *
219 * If for some mysterious reason, file size is greater than the value set in preferences, just use the whole file. If the user
220 * wants to reduce the file size, (s)he may just go to preferences and there it should be handled (by removing old file)
221 */
222 File diskCacheFile = new File(cachePath + File.separator + newCacheName + ".data");
223 if (diskCacheFile.exists()) {
224 blockAttr.setMaxKeySize((int) Math.max(maxDiskObjects, diskCacheFile.length()/1024));
225 } else {
226 blockAttr.setMaxKeySize(maxDiskObjects);
227 }
228 blockAttr.setBlockSizeBytes(4096); // use 4k blocks
229 ret = blockAttr;
230 } else {
231 IndexedDiskCacheAttributes indexAttr = new IndexedDiskCacheAttributes();
232 indexAttr.setMaxKeySize(maxDiskObjects);
233 ret = indexAttr;
234 }
235 ret.setDiskLimitType(IDiskCacheAttributes.DiskLimitType.SIZE);
236 File path = new File(cachePath);
237 if (!path.exists() && !path.mkdirs()) {
238 Logging.warn("Failed to create cache path: {0}", cachePath);
239 } else {
240 ret.setDiskPath(cachePath);
241 }
242 ret.setCacheName(newCacheName);
243
244 return ret;
245 }
246
247 private static void removeStaleFiles(String basePathPart, String suffix) {
248 deleteCacheFiles(basePathPart + suffix);
249 }
250
251 private static void deleteCacheFiles(String basePathPart) {
252 Utils.deleteFileIfExists(new File(basePathPart + ".key"));
253 Utils.deleteFileIfExists(new File(basePathPart + ".data"));
254 }
255
256 private static CompositeCacheAttributes getCacheAttributes(int maxMemoryElements) {
257 CompositeCacheAttributes ret = new CompositeCacheAttributes();
258 ret.setMaxObjects(maxMemoryElements);
259 ret.setDiskUsagePattern(DiskUsagePattern.UPDATE);
260 return ret;
261 }
262}
Note: See TracBrowser for help on using the repository browser.