KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > fjank > jcache > CacheImpl


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 JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.lang.ref.ReferenceQueue JavaDoc;
25 import java.net.InetAddress JavaDoc;
26 import java.net.URL JavaDoc;
27 import java.util.Enumeration JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Map JavaDoc;
32 import java.util.Properties JavaDoc;
33 import java.util.Vector JavaDoc;
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 JavaDoc userRegions = new HashMap JavaDoc();
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 JavaDoc refQueue = new ReferenceQueue JavaDoc();
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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc logger) throws CacheException {
246         try {
247             return (CacheLogger) Class.forName(logger).newInstance();
248         } catch (ClassNotFoundException JavaDoc e) {
249             throw new CacheException("The CacheLogger " + logger + " could not be found.");
250         } catch (InstantiationException JavaDoc e) {
251             throw new CacheException("The CacheLogger " + logger + " could not be loaded.");
252         } catch (IllegalAccessException JavaDoc 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 JavaDoc logSeverity) {
265         try {
266             java.lang.reflect.Field JavaDoc[] 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 JavaDoc 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 JavaDoc parseAddresses(final String JavaDoc addresses) {
286         java.util.ArrayList JavaDoc returnValue = new java.util.ArrayList JavaDoc();
287         java.util.StringTokenizer JavaDoc tokenizer = new java.util.StringTokenizer JavaDoc(addresses, ",");
288         while (tokenizer.hasMoreTokens()) {
289             try {
290                 returnValue.add(new java.net.URL JavaDoc("http://" + tokenizer.nextToken()));
291             } catch (java.net.MalformedURLException JavaDoc 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 JavaDoc configFile) {
308         InputStream JavaDoc in = null;
309         try {
310             synchronized (_singleton) {
311                 if (!ready) {
312                     Properties JavaDoc properties = new Properties JavaDoc();
313                     if (configFile == null) {
314                         in = getClass().getClassLoader().getResourceAsStream("./jcache.properties");
315                     } else {
316                         in = new FileInputStream JavaDoc(configFile);
317                     }
318                     if (in == null) {
319                         properties = new Properties JavaDoc();
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 JavaDoc logFileName = properties.getProperty("logFileName", "jcache.log");
327                     int cleanInterval = 30;
328                     try {
329                         cleanInterval = Integer.parseInt(properties.getProperty("cleanInterval", "30"), 10);
330                     } catch (NumberFormatException JavaDoc e) {
331                     }
332                     int diskSize = 10;
333                     try {
334                         diskSize = Integer.parseInt(properties.getProperty("diskSize", "10"), 10);
335                     } catch (NumberFormatException JavaDoc e) {
336                     }
337                     String JavaDoc diskPath = properties.getProperty("diskPath");
338                     float version = (float) 1.0;
339                     try {
340                         version = Float.parseFloat(properties.getProperty("version", "1.0"));
341                     } catch (NumberFormatException JavaDoc e) {
342                     }
343                     int maxObjects = 5000;
344                     try {
345                         maxObjects = Integer.parseInt(properties.getProperty("maxObjects", "5000"), 10);
346                     } catch (NumberFormatException JavaDoc e) {
347                     }
348                     int maxSize = -1;
349                     try {
350                         maxSize = Integer.parseInt(properties.getProperty("maxSize", "1"), 10);
351                     } catch (NumberFormatException JavaDoc 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 JavaDoc 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 JavaDoc iter = addresses.iterator();
377                     while (iter.hasNext()) {
378                         URL JavaDoc url = (URL JavaDoc) iter.next();
379                         attributes.addCacheAddr(InetAddress.getByName(url.getHost()), url.getPort());
380                     }
381                     init(attributes);
382                 }
383             }
384         } catch (IOException JavaDoc e) {
385             throw new IllegalStateException JavaDoc("Error loading configuration from properties file. Caused by:" + e.getMessage());
386         } catch (CacheNotAvailableException e) {
387             throw new IllegalStateException JavaDoc("The cache was not available. "+e.getMessage());
388         } finally {
389             if (in != null) {
390                 try {
391                     in.close();
392                 } catch (IOException JavaDoc e1) {
393                     throw new IllegalStateException JavaDoc("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 JavaDoc iter = userRegions.keySet().iterator();
438         while (iter.hasNext()) {
439             Object JavaDoc 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 JavaDoc listCacheObjects() {
506         Vector JavaDoc temp = new Vector JavaDoc();
507         addNamedCacheObjects(temp, CacheRegion.getRegion());
508         Iterator JavaDoc 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 JavaDoc vector, final CacheRegion region) {
522         recurseObjects(vector, region);
523     }
524
525     private void recurseObjects(final Vector JavaDoc vector, final CacheGroup group) {
526         Map JavaDoc objects = group.weakReferenceObjects;
527         Iterator JavaDoc iter = objects.keySet().iterator();
528         while (iter.hasNext()) {
529             vector.add(new CacheObjectInfoImpl((CacheObject) objects.get(iter.next())));
530         }
531         Map JavaDoc groups = group.getGroups();
532         for (Iterator JavaDoc iterator = groups.keySet().iterator(); iterator.hasNext();) {
533             Object JavaDoc 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 JavaDoc listCacheObjects(final String JavaDoc region) throws RegionNotFoundException {
556         Vector JavaDoc temp = new Vector JavaDoc();
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 JavaDoc 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 JavaDoc 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 JavaDoc userRegionNames() {
630         return userRegions.keySet().iterator();
631     }
632     public DistributionEngine getDistributionEngine() {
633         return distributionEngine;
634     }
635 }
Popular Tags