001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ha.framework.server.util;
023:
024: import java.io.Serializable;
025: import java.util.Collection;
026: import java.util.Iterator;
027: import java.util.Timer;
028: import java.util.TimerTask;
029: import javax.naming.InitialContext;
030:
031: import org.jboss.ha.framework.interfaces.DistributedState;
032: import org.jboss.ha.framework.interfaces.HAPartition;
033: import org.jboss.logging.Logger;
034: import org.jboss.util.CachePolicy;
035:
036: /** An implementation of a timed cache. This is a cache whose entries have a
037: limited lifetime with the ability to refresh their lifetime. The entries
038: managed by the cache implement the TimedCachePolicy.TimedEntry interface. If
039: an object inserted into the cache does not implement this interface, it will
040: be wrapped in a DefaultTimedEntry and will expire without the possibility of
041: refresh after getDefaultLifetime() seconds.
042:
043: This is a lazy cache policy in that objects are not checked for expiration
044: until they are accessed.
045:
046: @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>.
047: @version $Revision: 57188 $
048: */
049: public class DistributedTimedCachePolicy extends TimerTask implements
050: CachePolicy {
051: /** The interface that cache entries support.
052: */
053: public static interface TimedEntry extends Serializable {
054: /** Initializes an entry with the current cache time. This is called when
055: the entry is first inserted into the cache so that entries do not
056: have to know the absolute system time.
057: */
058: public void init(long now);
059:
060: /** Is the entry still valid basis the current time
061: @return true if the entry is within its lifetime, false if it is expired.
062: */
063: public boolean isCurrent(long now);
064:
065: /** Attempt to extend the entry lifetime by refreshing it.
066: @return true if the entry was refreshed successfully, false otherwise.
067: */
068: public boolean refresh();
069:
070: /** Notify the entry that it has been removed from the cache.
071: */
072: public void destroy();
073:
074: /** Get the value component of the TimedEntry. This may or may not
075: be the TimedEntry implementation.
076: */
077: public Object getValue();
078: }
079:
080: protected static Timer resolutionTimer = new Timer(true);
081: protected static Logger log = Logger
082: .getLogger(DistributedTimedCachePolicy.class);
083:
084: /** The map of cached TimedEntry objects. */
085: protected DistributedState entryMap;
086: protected String category;
087: protected String partitionName;
088: /** The lifetime in seconds to use for objects inserted
089: that do not implement the TimedEntry interface. */
090: protected int defaultLifetime;
091: /** The caches notion of the current time */
092: protected long now;
093: /** The resolution in seconds of the cach current time */
094: protected int resolution;
095:
096: /** Creates a new TimedCachePolicy with the given default entry lifetime
097: that does not synchronized access to its policy store and uses a 60
098: second resolution.
099: */
100: public DistributedTimedCachePolicy(String category,
101: String partitionName, int defaultLifetime) {
102: this (category, partitionName, defaultLifetime, 0);
103: }
104:
105: /** Creates a new TimedCachePolicy with the given default entry lifetime
106: that does/does not synchronized access to its policy store depending
107: on the value of threadSafe.
108: @param category the name of the catetegory used in the DistributedState
109: access calls.
110: @param partitionName the name of the HAPartition who's replicated
111: state service will be used as the cache store.
112: @param defaultLifetime the lifetime in seconds to use for objects inserted
113: that do not implement the TimedEntry interface.
114: @param resolution the resolution in seconds of the cache timer. A cache does
115: not query the system time on every get() invocation. Rather the cache
116: updates its notion of the current time every 'resolution' seconds.
117: @see DistributedState
118: */
119: public DistributedTimedCachePolicy(String category,
120: String partitionName, int defaultLifetime, int resolution) {
121: this .category = category;
122: this .partitionName = partitionName;
123: this .defaultLifetime = defaultLifetime;
124: if (resolution <= 0)
125: resolution = 60;
126: this .resolution = resolution;
127: }
128:
129: // Service implementation ----------------------------------------------
130: /** Initializes the cache for use. Prior to this the cache has no store.
131: */
132: public void create() throws Exception {
133: // Lookup the parition
134: InitialContext ctx = new InitialContext();
135: String jndiName = "/HAPartition/" + partitionName;
136: HAPartition partition = (HAPartition) ctx.lookup(jndiName);
137: this .entryMap = partition.getDistributedStateService();
138: log.debug("Obtained DistributedState from partition="
139: + partitionName);
140: now = System.currentTimeMillis();
141: }
142:
143: /** Schedules this with the class resolutionTimer Timer object for
144: execution every resolution seconds.
145: */
146: public void start() {
147: resolutionTimer.scheduleAtFixedRate(this , 0, 1000 * resolution);
148: }
149:
150: /** Stop cancels the resolution timer and flush()es the cache.
151: */
152: public void stop() {
153: super .cancel();
154: }
155:
156: /** Clears the cache of all entries.
157: */
158: public void destroy() {
159: }
160:
161: // --- Begin CachePolicy interface methods
162: /** Get the cache value for key if it has not expired. If the TimedEntry
163: is expired its destroy method is called and then removed from the cache.
164: @return the TimedEntry value or the original value if it was not an
165: instance of TimedEntry if key is in the cache, null otherwise.
166: */
167: public Object get(Object key) {
168: Serializable skey = (Serializable) key;
169: TimedEntry entry = (TimedEntry) entryMap.get(category, skey);
170: if (entry == null)
171: return null;
172:
173: if (entry.isCurrent(now) == false) { // Try to refresh the entry
174: if (entry.refresh() == false) { // Failed, remove the entry and return null
175: entry.destroy();
176: try {
177: entryMap.remove(category, skey);
178: } catch (Exception e) {
179: log.debug("Failed to remove expired entry", e);
180: }
181: return null;
182: }
183: }
184: Object value = entry.getValue();
185: return value;
186: }
187:
188: /** Get the cache value for key. This method does not check to see if
189: the entry has expired.
190: @return the TimedEntry value or the original value if it was not an
191: instancee of TimedEntry if key is in the cache, null otherwise.
192: */
193: public Object peek(Object key) {
194: Serializable skey = (Serializable) key;
195: TimedEntry entry = (TimedEntry) entryMap.get(category, skey);
196: Object value = null;
197: if (entry != null)
198: value = entry.getValue();
199: return value;
200: }
201:
202: /** Insert a value into the cache. In order to have the cache entry
203: reshresh itself value would have to implement TimedEntry and
204: implement the required refresh() method logic.
205: @param key the key for the cache entry
206: @param value Either an instance of TimedEntry that will be inserted without
207: change, or an abitrary value that will be wrapped in a non-refreshing
208: TimedEntry.
209: */
210: public void insert(Object key, Object value) {
211: Serializable skey = (Serializable) key;
212: TimedEntry entry = (TimedEntry) entryMap.get(category, skey);
213: if (entry != null)
214: throw new IllegalStateException(
215: "Attempt to insert duplicate entry");
216: if ((value instanceof TimedEntry) == false) { // Wrap the value in a DefaultTimedEntry
217: Serializable svalue = (Serializable) value;
218: entry = new DefaultTimedEntry(defaultLifetime, svalue);
219: } else {
220: entry = (TimedEntry) value;
221: }
222:
223: entry.init(now);
224: try {
225: entryMap.set(category, skey, entry);
226: } catch (Exception e) {
227: log.error("Failed to set entry", e);
228: }
229: }
230:
231: /** Remove the entry associated with key and call destroy on the entry
232: if found.
233: */
234: public void remove(Object key) {
235: Serializable skey = (Serializable) key;
236: try {
237: TimedEntry entry = (TimedEntry) entryMap.remove(category,
238: skey);
239: if (entry != null)
240: entry.destroy();
241: } catch (Exception e) {
242: log.error("Failed to remove entry", e);
243: }
244: }
245:
246: /** Remove all entries from the cache.
247: */
248: public void flush() {
249: Collection keys = entryMap.getAllKeys(category);
250: // Notify the entries of their removal
251: Iterator iter = keys.iterator();
252: while (iter.hasNext()) {
253: Serializable key = (Serializable) iter.next();
254: TimedEntry entry = (TimedEntry) entryMap.get(category, key);
255: entry.destroy();
256: }
257: }
258:
259: public int size() {
260: return entryMap.getAllKeys(category).size();
261: }
262:
263: // --- End CachePolicy interface methods
264:
265: /** Get the default lifetime of cache entries.
266: @return default lifetime in seconds of cache entries.
267: */
268: public int getDefaultLifetime() {
269: return defaultLifetime;
270: }
271:
272: /** Set the default lifetime of cache entries for new values added to the cache.
273: @param defaultLifetime lifetime in seconds of cache values that do
274: not implement TimedEntry.
275: */
276: public void setDefaultLifetime(int defaultLifetime) {
277: this .defaultLifetime = defaultLifetime;
278: }
279:
280: /** The TimerTask run method. It updates the cache time to the
281: current system time.
282: */
283: public void run() {
284: now = System.currentTimeMillis();
285: }
286:
287: /** Get the cache time.
288: @return the cache time last obtained from System.currentTimeMillis()
289: */
290: public long currentTimeMillis() {
291: return now;
292: }
293:
294: /** Get the raw TimedEntry for key without performing any expiration check.
295: @return the TimedEntry value associated with key if one exists, null otherwise.
296: */
297: public TimedEntry peekEntry(Object key) {
298: Serializable skey = (Serializable) key;
299: TimedEntry entry = (TimedEntry) entryMap.get(category, skey);
300: return entry;
301: }
302:
303: /** The default implementation of TimedEntry used to wrap non-TimedEntry
304: objects inserted into the cache.
305: */
306: static class DefaultTimedEntry implements TimedEntry {
307: long expirationTime;
308: Serializable value;
309:
310: DefaultTimedEntry(long lifetime, Serializable value) {
311: this .expirationTime = 1000 * lifetime;
312: this .value = value;
313: }
314:
315: public void init(long now) {
316: expirationTime += now;
317: }
318:
319: public boolean isCurrent(long now) {
320: return expirationTime > now;
321: }
322:
323: public boolean refresh() {
324: return false;
325: }
326:
327: public void destroy() {
328: }
329:
330: public Object getValue() {
331: return value;
332: }
333: }
334: }
|