View Javadoc

1   /*
2    *  Copyright 2004 David C. Brown
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package net.sf.ashkay;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  
24  import EDU.oswego.cs.dl.util.concurrent.Latch;
25  import org.apache.log4j.Logger;
26  
27  
28  /***
29   * ObjectCache is the default implementation of an object caching mechanism.
30   * <br><br>
31   * ObjectCaching makes use of a Factory for the creation of objects that are not
32   * cached. The user of an ObjectCache provides an implementation of
33   * ObjectFactory to the cache. If the cache does not find the object asked for,
34   * it uses the factory to create the object, cache it, and return it.
35   * <br><br>
36   * Looking up an object from cache requires a key. The key is a unique
37   * identifier. The key could be a database record id, a URI, or any other id
38   * that is guaranteed to be unique. The key is also used by the factory to
39   * create the object. The factory ought to be able to construct an object given
40   * only a key. This key is then used as the key in the cache.
41   * <br><br>
42   * Gets may also be done with a data parameter. The data provides construction
43   * information to the factory about the object being created. The cache does not
44   * use the data directly but, if the object is not cached, will pass the data
45   * to the factory. Common use of the data would be if the user of the cache
46   * has an "un-objectified" data source (e.g. XML document) from some other
47   * source that would save the factory the lookup call. So, if the user has the
48   * key and the data and wants the object form of the data, it simply asks the
49   * cache passing in the key and data.
50   * <br><br>
51   * The ObjectCache uses CachingStrategies to detirmine a strategy for caching
52   * objects that is stores. These strategies may affect caching in any number of
53   * ways. You might want to use a cache that does not prevent garbage collection
54   * of cached objects, only getting benefit of the cache as memory allows. A
55   * client may want to place timeouts for objects to force them to be re-loaded
56   * after a period of time. Any number of caching strategies may be used, but be
57   * careful not to use two caching strategies that work in opposition. That is
58   * a check left to the user at this point.
59   *
60   * @author <a href="mailto:bangroot@users.sf.net">Dave Brown</a>
61   */
62  public class ObjectCache
63  {
64  	private static final Logger LOG = Logger.getLogger(ObjectCache.class);
65  
66  	private ObjectFactory factory;
67  	private List strategies = new ArrayList();
68  	private Map cache = new HashMap();
69  
70  	public ObjectCache()
71  	{
72  		this(null, new ArrayList());
73  	}
74  
75  	/***
76  	 * Constructs an ObjectCache from a factory using the default strategy of
77  	 * NONE.
78  	 *
79  	 * @param aFactory - the factory for the cache to use
80  	 */
81  
82  	public ObjectCache(ObjectFactory aFactory)
83  	{
84  		this(aFactory, new ArrayList());
85  	}
86  
87  	/***
88  	 * Constructs an ObjectCache without a factory using the strategies indicated.
89  	 *
90  	 * @param theStrategies
91  	 */
92  
93  	public ObjectCache(List theStrategies)
94  	{
95  		this(null, theStrategies);
96  	}
97  
98  	/***
99  	 * Constructs an ObjectCache from a factory and strategy.
100 	 *
101 	 * @param aFactory      - the factory for the cache to use
102 	 * @param theStrategies - the caching strategies for this cache
103 	 */
104 	public ObjectCache(ObjectFactory aFactory, List theStrategies)
105 	{
106 		factory = aFactory;
107 		strategies = theStrategies;
108 	}
109 
110 	/***
111 	 * Adds a caching strategy to this cache
112 	 *
113 	 * @param aStrategy - the strategy to add
114 	 */
115 	public ObjectCache addStrategy(CachingStrategy aStrategy)
116 	{
117 		synchronized (strategies)
118 		{
119 			strategies.add(aStrategy);
120 		}
121 
122 		return this;
123 	}
124 
125 	/***
126 	 * Removes a strategy from this cache
127 	 *
128 	 * @param aStrategy - the strategy to remove
129 	 */
130 	public ObjectCache removeStrategy(CachingStrategy aStrategy)
131 	{
132 		synchronized (strategies)
133 		{
134 			strategies.remove(aStrategy);
135 		}
136 
137 		return this;
138 	}
139 
140 	/***
141 	 * Checks if this cache uses the specified caching strategy
142 	 *
143 	 * @param aStrategy - the strategy to check
144 	 */
145 	public boolean usesStrategy(CachingStrategy aStrategy)
146 	{
147 		synchronized (strategies)
148 		{
149 			return strategies.contains(aStrategy);
150 		}
151 	}
152 
153 	/***
154 	 * Finds an object in the cache and returns it. If the cache contains no
155 	 * object for the specified key, the cache attempts to construct it using
156 	 * its factory.
157 	 *
158 	 * @param key - the key to lookup
159 	 * @throws CreationException on any error during creation
160 	 * @see #get(java.lang.Object, java.lang.Object)
161 	 */
162 	public Object get(Object key) throws CreationException
163 	{
164 		return this.get(key, null);
165 	}
166 
167 	/***
168 	 * Forcefully evicts an object/key from the cache. This will only evict the
169 	 * object if it has been created (i.e. not being created by a factory).
170 	 *
171 	 * @param key to the object to evict
172 	 * @return the object evicted or null if none
173 	 */
174 	public Object evict(Object key)
175 	{
176 		Object val = null;
177 		Object entry = cache.get(key);
178 		if (entry instanceof CacheEntry)
179 		{
180 			val = ((CacheEntry) entry).getEntryObject();
181 			synchronized (cache)
182 			{
183 				cache.remove(key);
184 			}
185 		}
186 		return val;
187 	}
188 
189 	/***
190 	 * Finds an object in the cache and returns it. If the cache contains no
191 	 * object for the specified key, the cache attempts to construct it using
192 	 * its factory and the passed in data. The cache also uses the caching
193 	 * strategies to validate the entry in the cache.
194 	 *
195 	 * @param key  - the key to lookup
196 	 * @param data - the data to aid construction
197 	 * @throws CreationException on any error during creation
198 	 */
199 	public Object get(Object key, Object data) throws CreationException
200 	{
201 		CacheEntry entry = null;
202 		entry = findCacheEntry(key);
203 
204 		boolean passes = validateEntry(entry);
205 
206 		if (!passes)
207 		{
208 			Latch buildLatch = null;
209 			buildLatch = prepareLatch(key);
210 			try
211 			{
212 				entry = createObjectFor(key, data);
213 			}
214 			finally
215 			{
216 				if (null == entry)
217 				{
218 					synchronized (cache)
219 					{
220 						cache.put(key, null);
221 					}
222 				}
223 				buildLatch.release();
224 			}
225 		}
226 
227 		Object val = null;
228 		if (null != entry)
229 		{
230 			val = entry.getEntryObject();
231 		}
232 
233 		return val;
234 	}
235 
236 	private CacheEntry createObjectFor(Object key, Object data) throws CreationException
237 	{
238 		CacheEntry entry = null;
239 
240 		if (null != factory)
241 		{
242 			Object tempO = factory.createObjectFor(key, data);
243 			if (null != tempO)
244 			{
245 				entry = new CacheEntry(key, tempO);
246 				entry.setCache(this);
247 				entry = prepareEntry(entry);
248 			}
249 		}
250 
251 		synchronized (cache)
252 		{
253 			cache.put(key, entry);
254 		}
255 
256 		return entry;
257 	}
258 
259 	private CacheEntry prepareEntry(CacheEntry entry)
260 	{
261 		synchronized (strategies)
262 		{
263 			Iterator strategyIter = strategies.iterator();
264 			while (strategyIter.hasNext())
265 			{
266 				CachingStrategy strat = (CachingStrategy) strategyIter.next();
267 				entry = strat.prepare(entry);
268 			}
269 		}
270 		return entry;
271 	}
272 
273 	private Latch prepareLatch(Object key)
274 	{
275 		Latch buildLatch;
276 		synchronized (cache)
277 		{
278 			buildLatch = new Latch();
279 			cache.put(key, buildLatch);
280 		}
281 		return buildLatch;
282 	}
283 
284 	private boolean validateEntry(CacheEntry entry)
285 	{
286 		boolean passes = false;
287 		if (entry != null)
288 		{
289 			passes = true;
290 			synchronized (strategies)
291 			{
292 				Iterator strategyIter = strategies.iterator();
293 				while (strategyIter.hasNext())
294 				{
295 					CachingStrategy strat = (CachingStrategy) strategyIter.next();
296 					if (!strat.validate(entry))
297 					{
298 						passes = false;
299 					}
300 				}
301 			}
302 		}
303 		return passes;
304 	}
305 
306 	private CacheEntry findCacheEntry(Object key) throws CreationException
307 	{
308 		CacheEntry entry = null;
309 		Object cacheObject = cache.get(key);
310 
311 		if (cacheObject != null)
312 		{
313 			if (cacheObject instanceof Latch)
314 			{
315 				try
316 				{
317 					((Latch) cacheObject).acquire();
318 				}
319 				catch (InterruptedException e)
320 				{
321 					throw new CreationException(e);
322 				}
323 
324 				entry = findCacheEntry(key);
325 			}
326 			else
327 			{
328 				entry = (CacheEntry) cacheObject;
329 			}
330 		}
331 
332 		return entry;
333 	}
334 
335 	public void put(Object key, Object value)
336 	{
337 		CacheEntry entry = new CacheEntry(key, value);
338 		entry.setCache(this);
339 		entry = prepareEntry(entry);
340 		cache.put(key, entry);
341 	}
342 
343 	/***
344 	 * Clears the cache
345 	 */
346 	public void clear()
347 	{
348 		cache.clear();
349 	}
350 
351 	/***
352 	 * Returns the size of the cache
353 	 *
354 	 * @return int size of the cache
355 	 */
356 	public int size()
357 	{
358 		return cache.size();
359 	}
360 }