1 /* Open Source Java Caching Service
2 * Copyright (C) 2002 Frank Karlstrøm
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 * The author can be contacted by email: fjankk@sourceforge.net
18 */
19 package org.fjank.jcache;
20
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.lang.ref.ReferenceQueue;
25 import java.net.InetAddress;
26 import java.net.URL;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.Vector;
34 import javax.util.jcache.Attributes;
35 import javax.util.jcache.CacheAttributes;
36 import javax.util.jcache.CacheException;
37 import javax.util.jcache.CacheLogger;
38 import javax.util.jcache.CacheNotAvailableException;
39 import javax.util.jcache.DiskCacheException;
40 import javax.util.jcache.NullObjectNameException;
41 import javax.util.jcache.ObjectExistsException;
42 import javax.util.jcache.RegionNotFoundException;
43 import org.fjank.jcache.distribution.DistributionEngine;
44 import org.fjank.jcache.persistence.DiskCache;
45
46 /**
47 * Contains several usefull methods for configuring, administering and
48 * monitoring the Cache. Is final to avoid subclassing to further lock down
49 * the singleton pattern.
50 *
51 * @author Frank Karlstrøm
52 *
53 * @todo fix Singleton serialization, Multiple classloaders, and JVM
54 * destroy/reload.
55 */
56 public final class CacheImpl implements javax.util.jcache.Cache {
57 private DistributionEngine distributionEngine;
58 /** this is the actuall cache instance in this JVM */
59 private static CacheImpl _singleton;
60 /** the disk cache implementation */
61 private DiskCache diskCache;
62 /** a boolean indication wether this cache is ready or not. */
63 private boolean ready;
64 /** the CacheAttributes for this cache */
65 private CacheAttributes attributes;
66 /** the version of this cache */
67 private float version;
68 /** the user defined regions. */
69 private final Map userRegions = new HashMap();
70 /**
71 * a ReferenceQueue. all objects which users either destroys, or quit
72 * using, ends up in this queue. (The GC sends them here)
73 */
74 private final ReferenceQueue refQueue = new ReferenceQueue();
75 /**
76 * starts the service Threads wich sweeps the cache to remove invalid
77 * objects.
78 */
79 //2004/09-FB
80 //private CacheSweeper sweeper;
81 /**
82 * A thread pool for running potential long-running background tasks.
83 */
84 private final JCacheExecutorPool execPool = new JCacheExecutorPool();
85
86 /**
87 * private constructor to implement the singleton pattern.
88 */
89 private CacheImpl() {
90 }
91
92 /**
93 * Gets an instance of the Cache class. Is synchronized to avoid several
94 * Threads to create multiple instances of this class wich MUST be a
95 * singleton.
96 *
97 * @param init a boolean inication wether to actually performe the
98 * initialization or not
99 *
100 * @return A Cache instance global for this JVM.
101 *
102 * @throws CacheNotAvailableException if the Cache is not available.
103 */
104 public static synchronized CacheImpl getCache(final boolean init) {
105 if (_singleton == null) {
106 _singleton = new CacheImpl();
107 if (init) {
108 _singleton.open(null);
109 }
110 }
111 return _singleton;
112 }
113
114 static CacheImpl getCache() {
115 return _singleton;
116 }
117 /**
118 * Gets the default region in this cache.,
119 *
120 * @return the default region in this cache.
121 */
122 public CacheRegion getRegion() {
123 return CacheRegion.getRegion();
124 }
125
126 /**
127 * Gets the named region in this cache.,
128 *
129 * @param name the name of the region to get.
130 *
131 * @return the named region.
132 */
133 public CacheRegion getRegion(final Object name) {
134 return (CacheRegion) this.userRegions.get(name);
135 }
136
137 /**
138 * Adds the specified region.
139 *
140 * @param name the name of the region to add.
141 * @param attributes the attributes for the new region.
142 *
143 * @throws ObjectExistsException if the name already exists in the cache.
144 * @throws NullObjectNameException if the region is attempted initialized
145 * with <CODE>null</CODE> as name.
146 */
147 void addRegion(final String name, final Attributes attributes) throws ObjectExistsException, NullObjectNameException {
148 if (name == null) {
149 throw new NullObjectNameException("A region cannot be created with null as its name.");
150 }
151 if ("".equals(name)) {
152 throw new NullObjectNameException("A region cannot be created with an empty string as its name.");
153 }
154 if (userRegions.containsKey(name)) {
155 throw new ObjectExistsException("The object " + name + " already exists in the cache.");
156 }
157 userRegions.put(name, new CacheRegion(name, new AttributesImpl(attributes)));
158 }
159
160 /**
161 * Will invalidate all objects in the named region and the named region.
162 *
163 * @param name the name of the region to destroy.
164 *
165 *
166 * @see #destroy()
167 */
168 public void destroyRegion(final Object name) {
169 CacheRegion localRegion = (CacheRegion) userRegions.get(name);
170 userRegions.remove(localRegion.getName());
171 localRegion.destroy();
172 }
173
174 /**
175 * initializes the cache, allocates space for metadata and starts the
176 * service threads. The cache is a process wide service, so it can only be
177 * initialized once per process. Subsequent init calls are ignored.
178 *
179 * @param attributes2 contains configuration information to initialize the
180 * cache system.
181 *
182 * @throws CacheNotAvailableException if the cache is not available.
183 */
184 public void init(final CacheAttributes attributes) throws CacheNotAvailableException{
185 synchronized (_singleton) {
186 if (!ready) {
187 this.attributes = attributes;
188 attributes.registerCache(this);
189 startServiceThreads();
190 if (attributes.getDiskPath() != null) {
191 try {
192 diskCache = new DiskCache(attributes);
193 } catch (DiskCacheException e) {
194 throw new CacheNotAvailableException(e);
195 }
196 }
197 if (attributes.isDistributed()) {
198 distributionEngine = DistributionEngine.getInstance(this);
199 }
200 ready = true;
201 }
202 }
203 }
204
205 /**
206 * starts the service Thread(s) which sweeps the cache to remove invalid
207 * objects.
208 */
209 private void startServiceThreads() {
210 //2004/09-FB
211 CacheSweeper.getInstance().startSweeper();
212 }
213
214 /**
215 * stops the service Threads which sweeps the cache to remove invalid
216 * objects.
217 */
218 private void stopServiceThreads() {
219 //2004/09-FB
220 CacheSweeper.getInstance().stopSweeper();
221 CacheSweeper.removeInstance();
222 }
223
224 /**
225 * will create a CacheAttributes object based on the values in a Java
226 * properties file, then call the method init. The properties file opened
227 * is called jcache.properties If this method is called, the init() method
228 * is not neccessary to call.
229 *
230 * @throws CacheNotAvailableException if the cache is not available.
231 */
232 public void open() throws CacheNotAvailableException {
233 open(null);
234 }
235
236 /**
237 * tries to initialize a CacheLogger frorm the named class.
238 *
239 * @param logger the name of the class to initialize.
240 *
241 * @return a CacheLogger instance of the class with the specified name.
242 *
243 * @throws CacheException if exceptions occur during initialization.
244 */
245 private CacheLogger parseLogger(final String logger) throws CacheException {
246 try {
247 return (CacheLogger) Class.forName(logger).newInstance();
248 } catch (ClassNotFoundException e) {
249 throw new CacheException("The CacheLogger " + logger + " could not be found.");
250 } catch (InstantiationException e) {
251 throw new CacheException("The CacheLogger " + logger + " could not be loaded.");
252 } catch (IllegalAccessException e) {
253 throw new CacheException("The CacheLogger " + logger + " is appearently not a CacheLogger.");
254 }
255 }
256
257 /**
258 * returns an int describing the CacheLoggerSeverity
259 *
260 * @param logSeverity a String representation of the log severity.
261 *
262 * @return an int describing the CacheLoggerSeverity
263 */
264 private int parseLogSeverity(final String logSeverity) {
265 try {
266 java.lang.reflect.Field[] fields = CacheLogger.class.getDeclaredFields();
267 for (int i = 0; i < fields.length; i++) {
268 if (fields[i].getName().equals(logSeverity)) {
269 return fields[i].getInt(null);
270 }
271 }
272 } catch (IllegalAccessException e) {
273 ;
274 }
275 return CacheLogger.DEFAULT;
276 }
277
278 /**
279 * parser the addresses into a List of URLs.
280 *
281 * @param addresses the lilst of addresses to parse.
282 *
283 * @return a List of URLS
284 */
285 private java.util.List parseAddresses(final String addresses) {
286 java.util.ArrayList returnValue = new java.util.ArrayList();
287 java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(addresses, ",");
288 while (tokenizer.hasMoreTokens()) {
289 try {
290 returnValue.add(new java.net.URL("http://" + tokenizer.nextToken()));
291 } catch (java.net.MalformedURLException e) {
292 e.printStackTrace();
293 }
294 }
295 return returnValue;
296 }
297
298 /**
299 * will create a CacheAttributes object based on the values in a Java
300 * properties file, then call the method init. If this method is called,
301 * the init() method is not neccessary to call.
302 *
303 * @param configFile the name of the configuration file.
304 *
305 * @throws CacheNotAvailableException if the cache is not available.
306 */
307 public void open(final String configFile) {
308 InputStream in = null;
309 try {
310 synchronized (_singleton) {
311 if (!ready) {
312 Properties properties = new Properties();
313 if (configFile == null) {
314 in = getClass().getClassLoader().getResourceAsStream("./jcache.properties");
315 } else {
316 in = new FileInputStream(configFile);
317 }
318 if (in == null) {
319 properties = new Properties();
320 } else {
321 properties.load(in);
322 in.close();
323 }
324 //convert the properties to legal values, use default if no props available.
325 boolean distribute = Boolean.valueOf(properties.getProperty("distribute", "false")).booleanValue();
326 String logFileName = properties.getProperty("logFileName", "jcache.log");
327 int cleanInterval = 30;
328 try {
329 cleanInterval = Integer.parseInt(properties.getProperty("cleanInterval", "30"), 10);
330 } catch (NumberFormatException e) {
331 }
332 int diskSize = 10;
333 try {
334 diskSize = Integer.parseInt(properties.getProperty("diskSize", "10"), 10);
335 } catch (NumberFormatException e) {
336 }
337 String diskPath = properties.getProperty("diskPath");
338 float version = (float) 1.0;
339 try {
340 version = Float.parseFloat(properties.getProperty("version", "1.0"));
341 } catch (NumberFormatException e) {
342 }
343 int maxObjects = 5000;
344 try {
345 maxObjects = Integer.parseInt(properties.getProperty("maxObjects", "5000"), 10);
346 } catch (NumberFormatException e) {
347 }
348 int maxSize = -1;
349 try {
350 maxSize = Integer.parseInt(properties.getProperty("maxSize", "1"), 10);
351 } catch (NumberFormatException e) {
352 }
353 int logSeverity = parseLogSeverity(properties.getProperty("logSeverity", "DEFAULT"));
354 CacheLogger logger = new DefaultCacheLogger();
355 try {
356 logger = parseLogger(properties.getProperty("logger", "org.fjank.jcache.DefaultCacheLogger"));
357 logger.setSeverity(logSeverity);
358 } catch (CacheException e) {
359 //ugh.
360 e.printStackTrace();
361 }
362 List addresses = parseAddresses(properties.getProperty("discoveryAddress", "localhost:12345"));
363 //is now parsed, create and populate the CacheAttributes.
364 CacheAttributes attributes = CacheAttributes.getDefaultCacheAttributes();
365 if (!distribute) {
366 attributes.setLocal();
367 }
368 attributes.setDefaultLogFileName(logFileName);
369 attributes.setCleanInterval(cleanInterval);
370 attributes.setDiskCacheSize(diskSize);
371 attributes.setDiskPath(diskPath);
372 attributes.setMaxObjects(maxObjects);
373 attributes.setMemoryCacheSize(maxSize);
374 this.version = version;
375 attributes.setLogger(logger);
376 Iterator iter = addresses.iterator();
377 while (iter.hasNext()) {
378 URL url = (URL) iter.next();
379 attributes.addCacheAddr(InetAddress.getByName(url.getHost()), url.getPort());
380 }
381 init(attributes);
382 }
383 }
384 } catch (IOException e) {
385 throw new IllegalStateException("Error loading configuration from properties file. Caused by:" + e.getMessage());
386 } catch (CacheNotAvailableException e) {
387 throw new IllegalStateException("The cache was not available. "+e.getMessage());
388 } finally {
389 if (in != null) {
390 try {
391 in.close();
392 } catch (IOException e1) {
393 throw new IllegalStateException("Failed to close stream to properties file. Caused by:" + e1.getMessage());
394 }
395 }
396 }
397 }
398
399 /**
400 * will mark the cache as "not ready" and shutdown the cache. Marking the
401 * cache as "not ready" will prevent any threads from accessing the Cache
402 * during shutdown. If the cache is distributed, close will unregister
403 * with the distributed caching system. The method should be called as a
404 * part of process termination.
405 */
406 public void close() {
407 synchronized (this) {
408 this.ready = false;
409 stopServiceThreads();
410 if (diskCache != null) {
411 diskCache.close();
412 }
413 }
414 }
415
416 /**
417 * will mark all objects in the cache, both disk and memory, as invalid,
418 * forcing objects to be reloaded. All processes sharing the disk cache
419 * are notified when the cache is flushed.
420 *
421 * @throws CacheException if an error occurs.
422 */
423 public void flush() throws CacheException {
424 flushMemory();
425 flushDisk();
426 }
427
428 /**
429 * will mark all objects in the cache as invalid, forcing objects to be
430 * reloaded. Flushing the memory cache will also invalidate memory objects
431 * spooled to disk. Objects that are only cached on disk will not be
432 * affected.
433 *
434 * @throws CacheException if an error occurs.
435 */
436 public void flushMemory() throws CacheException {
437 Iterator iter = userRegions.keySet().iterator();
438 while (iter.hasNext()) {
439 Object name = iter.next();
440 CacheRegion reg = (CacheRegion) userRegions.get(name);
441 reg.invalidate();
442 }
443 CacheRegion.getRegion().invalidate();
444 }
445
446 /**
447 * wll mark all objects in the cache as invalid, forcing objects to be
448 * reloaded. Flushing the disk cache will also invalidate memory objects
449 * spooled to disk. All processes sharing the disk cache are notified when
450 * the cache is flushed.
451 *
452 * @throws CacheException if an error occurs.
453 */
454 public void flushDisk() throws CacheException {
455 if (this.diskCache == null) {
456 return;
457 }
458 diskCache.removeAll();
459 }
460
461 /**
462 * returns the current version of the cache.
463 *
464 * @return the current version of the cache.
465 */
466 public float getVersion() {
467 return version;
468 }
469
470 /**
471 * returns true if the cache has been initialized and not closed, false
472 * otherwise.
473 *
474 * @return true if the cache has been initialized and not closed, false
475 * otherwise.
476 */
477 public boolean isReady() {
478 return this.ready;
479 }
480
481 /**
482 * returns true if the cache is currently in distributed mode, that it is
483 * distributing updates and invalidates within the site, false if all
484 * cache actions are local only.
485 *
486 * @return true if the cache is currently in distributed mode, that it is
487 * distributing updates and invalidates within the site, false if
488 * all cache actions are local only.
489 */
490 public boolean isDistributed() {
491 return this.attributes.isDistributed();
492 }
493
494 /**
495 * will return an Enumeration of CacheObjectInfo objects describing the
496 * objects in all regions in the cache. CacheObjectInfo will include
497 * information such as the object name, the type, what group it is
498 * associated with, the reference count, the expiration time if any and
499 * object attributes.
500 *
501 * @return an Enumeration of CacheObjectInfo objects.
502 *
503 *@todo add feature to list objects in the disk cache.
504 */
505 public Enumeration listCacheObjects() {
506 Vector temp = new Vector();
507 addNamedCacheObjects(temp, CacheRegion.getRegion());
508 Iterator iter = userRegions.keySet().iterator();
509 while (iter.hasNext()) {
510 addNamedCacheObjects(temp, (CacheRegion) userRegions.get(iter.next()));
511 }
512 return temp.elements();
513 }
514
515 /**
516 * adds all objects in the region to the vector.
517 * will also add from all the groups, if any.
518 * @param vector the vector to add all objects to.
519 * @param region the region to add from.
520 */
521 private void addNamedCacheObjects(final Vector vector, final CacheRegion region) {
522 recurseObjects(vector, region);
523 }
524
525 private void recurseObjects(final Vector vector, final CacheGroup group) {
526 Map objects = group.weakReferenceObjects;
527 Iterator iter = objects.keySet().iterator();
528 while (iter.hasNext()) {
529 vector.add(new CacheObjectInfoImpl((CacheObject) objects.get(iter.next())));
530 }
531 Map groups = group.getGroups();
532 for (Iterator iterator = groups.keySet().iterator(); iterator.hasNext();) {
533 Object key = iterator.next();
534 CacheGroup tmp = (CacheGroup) groups.get(key);
535 recurseObjects(vector, tmp);
536 }
537 }
538
539 /**
540 * will return an Enumeration of CacheObjectInfo objects describing the
541 * objects in the specified in the cache. CacheObjectInfo will include
542 * information such as the object name, the type, what group it is
543 * associated with, the reference count, the expiration time if any and
544 * object attributes.
545 *
546 * @param region the region to get the Enumeration for.
547 *
548 * @return an Enumeration of CacheObjectInfo objects.
549 *
550 * @throws RegionNotFoundException if the named region os not present in
551 * the cache.
552 *
553 *@todo add feature to list objects in the disk cache.
554 */
555 public Enumeration listCacheObjects(final String region) throws RegionNotFoundException {
556 Vector temp = new Vector();
557 if (region == null) {
558 throw new RegionNotFoundException("The regionName cannot be null.");
559 }
560 if (!userRegions.containsKey(region)) {
561 throw new RegionNotFoundException("The region " + region + " is not present in the cache.");
562 }
563 addNamedCacheObjects(temp, (CacheRegion) userRegions.get(region));
564 return temp.elements();
565 }
566
567 /**
568 * returns the current attributes of the cache including the cache version
569 * number, wether the cache is local or distributed, the maximum number of
570 * objects in the cache, the disk cache location, and the disk cache size.
571 *
572 * @return the current attributes of this cache.
573 *
574 * @throws CacheNotAvailableException if the cache is not ready.
575 */
576 public CacheAttributes getAttributes(){
577 return this.attributes;
578 }
579
580 /**
581 * sets the log severity of the cache system. This determines wich messages
582 * the cache formats and logs into the log destination. Severity's are
583 * defined in the CacheLogger class.
584 *
585 * @param severity the severity level to set
586 */
587 public void setLogSeverity(final int severity) {
588 this.attributes.getLogger().setSeverity(severity);
589 }
590
591 /**
592 * returns a String representation of this Cache.
593 *
594 * @return a String representation of this Cache.
595 */
596 public String toString() {
597 return "Fjanks FKache version " + getVersion() + " running in " + (isDistributed() ? "distributed" : "local") + " mode is " + (isReady() ? "" : "not ")
598 + "ready.";
599 }
600
601 /**
602 * gets the diskCache
603 *
604 * @return the diskCache
605 */
606 DiskCache getDiskCache() {
607 return diskCache;
608 }
609
610 /**
611 * gets the ReferenceQueue.
612 *
613 * @return the ReferenceQueue.
614 */
615 public ReferenceQueue getReferenceQueue() {
616 return refQueue;
617 }
618
619 /**
620 * @return
621 */
622 public JCacheExecutorPool getExecPool() {
623 return execPool;
624 }
625
626 /**Returns an Iterator over the user defined regions.
627 * @return an Iterator over the user defined regions.
628 */
629 Iterator userRegionNames() {
630 return userRegions.keySet().iterator();
631 }
632 public DistributionEngine getDistributionEngine() {
633 return distributionEngine;
634 }
635 }