View Javadoc

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 }