001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.store;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.component.ComponentException;
021: import org.apache.avalon.framework.component.ComponentManager;
022: import org.apache.avalon.framework.component.Composable;
023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
024: import org.apache.avalon.framework.parameters.Parameters;
025: import org.apache.avalon.framework.parameters.ParameterException;
026: import org.apache.avalon.framework.parameters.Parameterizable;
027: import org.apache.avalon.framework.thread.ThreadSafe;
028:
029: import org.apache.cocoon.util.ClassUtils;
030: import org.apache.cocoon.util.MRUBucketMap;
031:
032: import java.io.IOException;
033: import java.net.URLEncoder;
034: import java.util.Enumeration;
035: import java.util.NoSuchElementException;
036: import java.util.Iterator;
037: import java.util.Map;
038:
039: /**
040: * This class provides a cache algorithm for the requested documents.
041: * It combines a HashMap and a LinkedList to create a so called MRU
042: * (Most Recently Used) cache.
043: *
044: * <p>This implementation is based on MRUBucketMap - map with efficient
045: * O(1) implementation of MRU removal policy.
046: *
047: * <p>TODO: Port improvments to the Excalibur implementation
048: *
049: * @deprecated Use the Avalon Excalibur Store instead.
050: *
051: * @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a>
052: * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
053: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
054: * @version CVS $Id: MRUMemoryStore.java 433543 2006-08-22 06:22:54Z crossley $
055: */
056: public final class MRUMemoryStore extends AbstractLogEnabled implements
057: Store, Parameterizable, Composable, Disposable, ThreadSafe {
058:
059: private int maxobjects;
060: private boolean persistent;
061: protected MRUBucketMap cache;
062: private Store persistentStore;
063: private StoreJanitor storeJanitor;
064: private ComponentManager manager;
065:
066: /**
067: * Get components of the ComponentManager
068: *
069: * @param manager The ComponentManager
070: */
071: public void compose(ComponentManager manager)
072: throws ComponentException {
073: this .manager = manager;
074: if (getLogger().isDebugEnabled()) {
075: getLogger().debug("Looking up " + Store.PERSISTENT_CACHE);
076: getLogger().debug("Looking up " + StoreJanitor.ROLE);
077: }
078: this .persistentStore = (Store) manager
079: .lookup(Store.PERSISTENT_CACHE);
080: this .storeJanitor = (StoreJanitor) manager
081: .lookup(StoreJanitor.ROLE);
082: }
083:
084: /**
085: * Initialize the MRUMemoryStore.
086: * A few options can be used:
087: * <UL>
088: * <LI>maxobjects: Maximum number of objects stored in memory (Default: 100 objects)</LI>
089: * <LI>use-persistent-cache: Use persistent cache to keep objects persisted after
090: * container shutdown or not (Default: false)</LI>
091: * </UL>
092: *
093: * @param params Store parameters
094: * @exception ParameterException
095: */
096: public void parameterize(Parameters params)
097: throws ParameterException {
098: this .maxobjects = params.getParameterAsInteger("maxobjects",
099: 100);
100: this .persistent = params.getParameterAsBoolean(
101: "use-persistent-cache", false);
102: if ((this .maxobjects < 1)) {
103: throw new ParameterException(
104: "MRUMemoryStore maxobjects must be at least 1!");
105: }
106:
107: this .cache = new MRUBucketMap((int) (this .maxobjects * 1.2));
108: this .storeJanitor.register(this );
109: }
110:
111: /**
112: * Dispose the component
113: */
114: public void dispose() {
115: if (this .manager != null) {
116: getLogger().debug("Disposing component!");
117:
118: if (this .storeJanitor != null)
119: this .storeJanitor.unregister(this );
120: this .manager.release(this .storeJanitor);
121: this .storeJanitor = null;
122:
123: // save all cache entries to filesystem
124: if (this .persistent) {
125: if (getLogger().isDebugEnabled()) {
126: getLogger().debug(
127: "Final cache size: " + this .cache.size());
128: }
129: for (Iterator i = this .cache.keySet().iterator(); i
130: .hasNext();) {
131: Object key = i.next();
132: try {
133: Object value = this .cache.remove(key);
134: if (checkSerializable(value)) {
135: persistentStore.store(getFileName(key
136: .toString()), value);
137: }
138: } catch (IOException ioe) {
139: getLogger().error("Error in dispose()", ioe);
140: }
141: }
142: }
143: this .manager.release(this .persistentStore);
144: this .persistentStore = null;
145: }
146:
147: this .manager = null;
148: }
149:
150: /**
151: * Store the given object in a persistent state. It is up to the
152: * caller to ensure that the key has a persistent state across
153: * different JVM executions.
154: *
155: * @param key The key for the object to store
156: * @param value The object to store
157: */
158: public void store(Object key, Object value) {
159: this .hold(key, value);
160: }
161:
162: /**
163: * This method holds the requested object in a HashMap combined
164: * with a LinkedList to create the MRU.
165: * It also stores objects onto the filesystem if configured.
166: *
167: * @param key The key of the object to be stored
168: * @param value The object to be stored
169: */
170: public void hold(Object key, Object value) {
171: if (getLogger().isDebugEnabled()) {
172: getLogger().debug("Holding object in memory:");
173: getLogger().debug(" key: " + key);
174: getLogger().debug(" value: " + value);
175: }
176:
177: /** ...first test if the max. objects in cache is reached... */
178: while (this .cache.size() >= this .maxobjects) {
179: /** ...ok, heapsize is reached, remove the last element... */
180: this .free();
181: }
182:
183: /** ..put the new object in the cache, on the top of course ... */
184: this .cache.put(key, value);
185: }
186:
187: /**
188: * Get the object associated to the given unique key.
189: *
190: * @param key The key of the requested object
191: * @return the requested object
192: */
193: public Object get(Object key) {
194: Object value = this .cache.get(key);
195: if (value != null) {
196: if (getLogger().isDebugEnabled()) {
197: getLogger().debug("Found key: " + key.toString());
198: }
199: return value;
200: }
201:
202: if (getLogger().isDebugEnabled()) {
203: getLogger().debug("NOT Found key: " + key.toString());
204: }
205:
206: /** try to fetch from filesystem */
207: if (this .persistent) {
208: value = this .persistentStore
209: .get(getFileName(key.toString()));
210: if (value != null) {
211: try {
212: this .hold(key, value);
213: return value;
214: } catch (Exception e) {
215: getLogger().error("Error in hold()!", e);
216: return null;
217: }
218: }
219: }
220:
221: return null;
222: }
223:
224: /**
225: * Remove the object associated to the given key.
226: *
227: * @param key The key of to be removed object
228: */
229: public void remove(Object key) {
230: if (getLogger().isDebugEnabled()) {
231: getLogger().debug("Removing object from store");
232: getLogger().debug(" key: " + key);
233: }
234: this .cache.remove(key);
235: if (this .persistent && key != null) {
236: this .persistentStore.remove(getFileName(key.toString()));
237: }
238: }
239:
240: /**
241: * Indicates if the given key is associated to a contained object.
242: *
243: * @param key The key of the object
244: * @return true if the key exists
245: */
246: public boolean containsKey(Object key) {
247: return cache.containsKey(key)
248: || (persistent && persistentStore.containsKey(key));
249: }
250:
251: /**
252: * Returns the list of used keys as an Enumeration.
253: *
254: * @return the enumeration of the cache
255: */
256: public Enumeration keys() {
257: return new Enumeration() {
258: private Iterator i = cache.keySet().iterator();
259:
260: public boolean hasMoreElements() {
261: return i.hasNext();
262: }
263:
264: public Object nextElement() {
265: return i.next();
266: }
267: };
268: }
269:
270: /**
271: * Returns count of the objects in the store, or -1 if could not be
272: * obtained.
273: */
274: public int size() {
275: return this .cache.size();
276: }
277:
278: /**
279: * Frees some of the fast memory used by this store.
280: * It removes the last element in the store.
281: */
282: public void free() {
283: try {
284: if (this .cache.size() > 0) {
285: // This can throw NoSuchElementException
286: Map.Entry node = this .cache.removeLast();
287: if (getLogger().isDebugEnabled()) {
288: getLogger().debug("Freeing cache.");
289: getLogger().debug(" key: " + node.getKey());
290: getLogger().debug(" value: " + node.getValue());
291: }
292:
293: if (this .persistent) {
294: // Swap object on fs.
295: if (checkSerializable(node.getValue())) {
296: try {
297: this .persistentStore.store(getFileName(node
298: .getKey().toString()), node
299: .getValue());
300: } catch (Exception e) {
301: getLogger().error(
302: "Error storing object on fs", e);
303: }
304: }
305: }
306: }
307: } catch (NoSuchElementException e) {
308: getLogger().warn("Concurrency error in free()", e);
309: } catch (Exception e) {
310: getLogger().error("Error in free()", e);
311: }
312: }
313:
314: /**
315: * This method checks if an object is seriazable.
316: *
317: * @param object The object to be checked
318: * @return true if the object is storeable
319: */
320: private boolean checkSerializable(Object object) {
321:
322: if (object == null)
323: return false;
324:
325: try {
326: String clazz = object.getClass().getName();
327: if ((clazz
328: .equals("org.apache.cocoon.caching.CachedEventObject"))
329: || (clazz
330: .equals("org.apache.cocoon.caching.CachedStreamObject"))
331: || (ClassUtils.implements Interface(clazz,
332: "org.apache.cocoon.caching.CacheValidity"))) {
333: return true;
334: } else {
335: return false;
336: }
337: } catch (Exception e) {
338: getLogger().error("Error in checkSerializable()!", e);
339: return false;
340: }
341: }
342:
343: /**
344: * This method puts together a filename for
345: * the object, which shall be stored on the
346: * filesystem.
347: *
348: * @param key The key of the object
349: * @return the filename of the key
350: */
351: private String getFileName(String key) {
352: return URLEncoder.encode(key.toString());
353: }
354: }
|