001: /**
002: * Copyright 2003-2007 Luck Consulting Pty Ltd
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */package net.sf.ehcache;
016:
017: import org.apache.commons.logging.Log;
018: import org.apache.commons.logging.LogFactory;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.IOException;
023: import java.io.ObjectInputStream;
024: import java.io.ObjectOutputStream;
025: import java.io.Serializable;
026:
027: /**
028: * A Cache Element, consisting of a key, value and attributes.
029: * <p/>
030: * From ehcache-1.2, Elements can have keys and values that are Serializable or Objects. To preserve backward
031: * compatibility, special accessor methods for Object keys and values are provided: {@link #getObjectKey()} and
032: * {@link #getObjectValue()}. If placing Objects in ehcache, developers must use the new getObject... methods to
033: * avoid CacheExceptions. The get... methods are reserved for Serializable keys and values.
034: *
035: * @author Greg Luck
036: * @version $Id: Element.java 525 2007-07-31 22:41:54Z gregluck $
037: * @noinspection SerializableHasSerializationMethods
038: */
039: public final class Element implements Serializable, Cloneable {
040: /**
041: * serial version
042: * Updated for version 1.2 and again for 1.2.1
043: */
044: private static final long serialVersionUID = 3343087714201120157L;
045:
046: private static final Log LOG = LogFactory.getLog(Element.class
047: .getName());
048:
049: private static final long ONE_SECOND = 1000L;
050:
051: /**
052: * the cache key.
053: */
054: private final Object key;
055:
056: /**
057: * the value.
058: */
059: private Object value;
060:
061: /**
062: * version of the element. System.currentTimeMillis() is used to compute version for updated elements. That
063: * way, the actual version of the updated element does not need to be checked.
064: */
065: private long version;
066:
067: /**
068: * The creation time.
069: */
070: private long creationTime;
071:
072: /**
073: * The last access time.
074: */
075: private long lastAccessTime;
076:
077: /**
078: * The next to last access time. Used by the expiry mechanism
079: */
080: private long nextToLastAccessTime;
081:
082: /**
083: * The number of times the element was hit.
084: */
085: private long hitCount;
086:
087: /**
088: * The amount of time for the element to live, in seconds. 0 indicates unlimited.
089: */
090: private int timeToLive;
091:
092: /**
093: * The amount of time for the element to idle, in seconds. 0 indicates unlimited.
094: */
095: private int timeToIdle;
096:
097: /**
098: * If there is an Element in the Cache and it is replaced with a new Element for the same key,
099: * then both the version number and lastUpdateTime should be updated to reflect that. The creation time
100: * will be the creation time of the new Element, not the original one, so that TTL concepts still work.
101: */
102: private long lastUpdateTime;
103:
104: /**
105: * Whether the element is eternal, i.e. never expires.
106: */
107: private boolean eternal;
108:
109: /**
110: * Whether any combination of eternal, TTL or TTI has been set.
111: */
112: private boolean lifespanSet;
113:
114: /**
115: * A full constructor.
116: * <p/>
117: * Creation time is set to the current time. Last Access Time and Previous To Last Access Time
118: * are not set.
119: *
120: * @since .4
121: */
122: public Element(Serializable key, Serializable value, long version) {
123: this ((Object) key, (Object) value, version);
124:
125: }
126:
127: /**
128: * A full constructor.
129: * <p/>
130: * Creation time is set to the current time. Last Access Time and Previous To Last Access Time
131: * are not set.
132: *
133: * @since 1.2
134: */
135: public Element(Object key, Object value, long version) {
136: this .key = key;
137: this .value = value;
138: this .version = version;
139: creationTime = System.currentTimeMillis();
140: hitCount = 0;
141: }
142:
143: /**
144: * A full constructor.
145: *
146: * @since 1.3
147: */
148: public Element(Object key, Object value, long version,
149: long creationTime, long lastAccessTime,
150: long nextToLastAccessTime, long lastUpdateTime,
151: long hitCount) {
152: this .key = key;
153: this .value = value;
154: this .version = version;
155: this .creationTime = creationTime;
156: this .lastAccessTime = lastAccessTime;
157: this .nextToLastAccessTime = nextToLastAccessTime;
158: this .lastUpdateTime = lastUpdateTime;
159: this .hitCount = hitCount;
160: }
161:
162: /**
163: * Constructor.
164: *
165: * @param key
166: * @param value
167: */
168: public Element(Serializable key, Serializable value) {
169: this ((Object) key, (Object) value, 1L);
170: }
171:
172: /**
173: * Constructor.
174: *
175: * @param key
176: * @param value
177: * @since 1.2
178: */
179: public Element(Object key, Object value) {
180: this (key, value, 1L);
181: }
182:
183: /**
184: * Gets the key attribute of the Element object.
185: *
186: * @return The key value. If the key is not Serializable, null is returned and an info log message emitted
187: * @see #getObjectKey()
188: */
189: public final Serializable getKey() {
190: Serializable keyAsSerializable;
191: try {
192: keyAsSerializable = (Serializable) key;
193: } catch (Exception e) {
194: throw new CacheException(
195: "The key "
196: + key
197: + " is not Serializable. Consider using Element#getObjectKey()");
198: }
199: return keyAsSerializable;
200: }
201:
202: /**
203: * Gets the key attribute of the Element object.
204: * <p/>
205: * This method is provided for those wishing to use ehcache as a memory only cache
206: * and enables retrieval of non-Serializable values from elements.
207: *
208: * @return The key as an Object. i.e no restriction is placed on it
209: * @see #getKey()
210: */
211: public final Object getObjectKey() {
212: return key;
213: }
214:
215: /**
216: * Gets the value attribute of the Element object.
217: *
218: * @return The value which must be Serializable. If not use {@link #getObjectValue}. If the value is not Serializable, null is returned and an info log message emitted
219: * @see #getObjectValue()
220: */
221: public final Serializable getValue() {
222: Serializable valueAsSerializable;
223: try {
224: valueAsSerializable = (Serializable) value;
225: } catch (Exception e) {
226: throw new CacheException(
227: "The value "
228: + value
229: + " for key "
230: + key
231: + " is not Serializable. Consider using Element#getObjectKey()");
232: }
233: return valueAsSerializable;
234: }
235:
236: /**
237: * Gets the value attribute of the Element object as an Object.
238: * <p/>
239: * This method is provided for those wishing to use ehcache as a memory only cache
240: * and enables retrieval of non-Serializable values from elements.
241: *
242: * @return The value as an Object. i.e no restriction is placed on it
243: * @see #getValue()
244: * @since 1.2
245: */
246: public final Object getObjectValue() {
247: return value;
248: }
249:
250: /**
251: * Equals comparison with another element, based on the key.
252: */
253: public final boolean equals(Object object) {
254: if (object == null || !(object instanceof Element)) {
255: return false;
256: }
257:
258: Element element = (Element) object;
259: if (key == null || element.getObjectKey() == null) {
260: return false;
261: }
262:
263: return key.equals(element.getObjectKey());
264: }
265:
266: /**
267: * Sets time to Live
268: *
269: * @param timeToLiveSeconds the number of seconds to live
270: */
271: public void setTimeToLive(int timeToLiveSeconds) {
272: this .timeToLive = timeToLiveSeconds;
273: lifespanSet = true;
274: }
275:
276: /**
277: * Sets time to idle
278: *
279: * @param timeToIdleSeconds the number of seconds to idle
280: */
281: public void setTimeToIdle(int timeToIdleSeconds) {
282: this .timeToIdle = timeToIdleSeconds;
283: lifespanSet = true;
284: }
285:
286: /**
287: * Gets the hascode, based on the key.
288: */
289: public final int hashCode() {
290: return key.hashCode();
291: }
292:
293: /**
294: * Sets the version attribute of the ElementAttributes object.
295: *
296: * @param version The new version value
297: */
298: public final void setVersion(long version) {
299: this .version = version;
300: }
301:
302: /**
303: * Gets the creationTime attribute of the ElementAttributes object.
304: *
305: * @return The creationTime value
306: */
307: public final long getCreationTime() {
308: return creationTime;
309: }
310:
311: /**
312: * Sets the creationTime attribute of the ElementAttributes object.
313: */
314: public final void setCreateTime() {
315: creationTime = System.currentTimeMillis();
316: }
317:
318: /**
319: * Gets the version attribute of the ElementAttributes object.
320: *
321: * @return The version value
322: */
323: public final long getVersion() {
324: return version;
325: }
326:
327: /**
328: * Gets the last access time.
329: * Access means a get. So a newly created {@link Element}
330: * will have a last access time equal to its create time.
331: */
332: public final long getLastAccessTime() {
333: return lastAccessTime;
334: }
335:
336: /**
337: * Gets the next to last access time.
338: *
339: * @see #getLastAccessTime()
340: */
341: public final long getNextToLastAccessTime() {
342: return nextToLastAccessTime;
343: }
344:
345: /**
346: * Gets the hit count on this element.
347: */
348: public final long getHitCount() {
349: return hitCount;
350: }
351:
352: /**
353: * Resets the hit count to 0 and the last access time to 0.
354: */
355: public final void resetAccessStatistics() {
356: lastAccessTime = 0;
357: nextToLastAccessTime = 0;
358: hitCount = 0;
359: }
360:
361: /**
362: * Sets the last access time to now.
363: */
364: public final void updateAccessStatistics() {
365: nextToLastAccessTime = lastAccessTime;
366: lastAccessTime = System.currentTimeMillis();
367: hitCount++;
368: }
369:
370: /**
371: * Sets the last access time to now.
372: */
373: public final void updateUpdateStatistics() {
374: lastUpdateTime = System.currentTimeMillis();
375: version = lastUpdateTime;
376: }
377:
378: /**
379: * Returns a {@link String} representation of the {@link Element}.
380: */
381: public final String toString() {
382: StringBuffer sb = new StringBuffer();
383:
384: sb.append("[ key = ").append(key).append(", value=").append(
385: value).append(", version=").append(version).append(
386: ", hitCount=").append(hitCount).append(
387: ", CreationTime = ").append(this .getCreationTime())
388: .append(", LastAccessTime = ").append(
389: this .getLastAccessTime()).append(" ]");
390:
391: return sb.toString();
392: }
393:
394: /**
395: * Clones an Element. A completely new object is created, with no common references with the
396: * existing one.
397: * <p/>
398: * This method will not work unless the Object is Serializable
399: * <p/>
400: * Warning: This can be very slow on large object graphs. If you use this method
401: * you should write a performance test to verify suitability.
402: *
403: * @return a new {@link Element}, with exactly the same field values as the one it was cloned from.
404: * @throws CloneNotSupportedException
405: */
406: public final Object clone() throws CloneNotSupportedException {
407: //Not used. Just to get code inspectors to shut up
408: super .clone();
409:
410: Element element = new Element(deepCopy(key), deepCopy(value),
411: version);
412: element.creationTime = creationTime;
413: element.lastAccessTime = lastAccessTime;
414: element.nextToLastAccessTime = nextToLastAccessTime;
415: element.hitCount = hitCount;
416: return element;
417: }
418:
419: private Object deepCopy(Object oldValue) {
420: Serializable newValue = null;
421: ByteArrayOutputStream bout = new ByteArrayOutputStream();
422: ObjectOutputStream oos = null;
423: ObjectInputStream ois = null;
424: try {
425: oos = new ObjectOutputStream(bout);
426: oos.writeObject(oldValue);
427: ByteArrayInputStream bin = new ByteArrayInputStream(bout
428: .toByteArray());
429: ois = new ObjectInputStream(bin);
430: newValue = (Serializable) ois.readObject();
431: } catch (IOException e) {
432: LOG
433: .error("Error cloning Element with key "
434: + key
435: + " during serialization and deserialization of value");
436: } catch (ClassNotFoundException e) {
437: LOG
438: .error("Error cloning Element with key "
439: + key
440: + " during serialization and deserialization of value");
441: } finally {
442: try {
443: if (oos != null) {
444: oos.close();
445: }
446: if (ois != null) {
447: ois.close();
448: }
449: } catch (Exception e) {
450: LOG.error("Error closing Stream");
451: }
452: }
453: return newValue;
454: }
455:
456: /**
457: * The size of this object in serialized form. This is not the same
458: * thing as the memory size, which is JVM dependent. Relative values should be meaningful,
459: * however.
460: * <p/>
461: * Warning: This method can be <b>very slow</b> for values which contain large object graphs.
462: * <p/>
463: * If the key or value of the Element is not Serializable, an error will be logged and 0 will be returned.
464: * @return The serialized size in bytes
465: */
466: public final long getSerializedSize() {
467:
468: if (!isSerializable()) {
469: return 0;
470: }
471: long size = 0;
472: ByteArrayOutputStream bout = new ByteArrayOutputStream();
473: ObjectOutputStream oos = null;
474: try {
475: oos = new ObjectOutputStream(bout);
476: oos.writeObject(this );
477: size = bout.size();
478: return size;
479: } catch (IOException e) {
480: LOG
481: .debug("Error measuring element size for element with key "
482: + key + ". Cause was: " + e.getMessage());
483: } finally {
484: try {
485: if (oos != null) {
486: oos.close();
487: }
488: } catch (Exception e) {
489: LOG.error("Error closing ObjectOutputStream");
490: }
491: }
492:
493: return size;
494: }
495:
496: /**
497: * Whether the element may be Serialized.
498: * <p/>
499: * While Element implements Serializable, it is possible to create non Serializable elements
500: * for use in MemoryStores. This method checks that an instance of Element really is Serializable
501: * and will not throw a NonSerializableException if Serialized.
502: *
503: * @return true if the element is Serializable
504: * @since 1.2
505: */
506: public final boolean isSerializable() {
507: return key instanceof Serializable
508: && value instanceof Serializable;
509: }
510:
511: /**
512: * Whether the element's key may be Serialized.
513: * <p/>
514: * While Element implements Serializable, it is possible to create non Serializable elements and/or
515: * non Serializable keys for use in MemoryStores.
516: * <p/>
517: * This method checks that an instance of an Element's key really is Serializable
518: * and will not throw a NonSerializableException if Serialized.
519: *
520: * @return true if the element's key is Serializable
521: * @since 1.2
522: */
523: public final boolean isKeySerializable() {
524: return key instanceof Serializable;
525: }
526:
527: /**
528: * If there is an Element in the Cache and it is replaced with a new Element for the same key,
529: * then both the version number and lastUpdateTime should be updated to reflect that. The creation time
530: * will be the creation time of the new Element, not the original one, so that TTL concepts still work.
531: *
532: * @return the time when the last update occured. If this is the original Element, the time will be null
533: */
534: public long getLastUpdateTime() {
535: return lastUpdateTime;
536: }
537:
538: /**
539: * An element is expired if the expiration time as given by {@link #getExpirationTime()} is in the past.
540: *
541: * @return true if the Element is expired, otherwise false. If no lifespan has been set for the Element it is
542: * considered not able to expire.
543: * @see #getExpirationTime()
544: */
545: public boolean isExpired() {
546: if (!lifespanSet) {
547: return false;
548: }
549:
550: long now = System.currentTimeMillis();
551: long expirationTime = getExpirationTime();
552:
553: return now > expirationTime;
554: }
555:
556: /**
557: * Returns the expiration time based on time to live. If this element also has a time to idle setting, the expiry
558: * time will vary depending on whether the element is accessed.
559: *
560: * @return the time to expiration
561: */
562: public long getExpirationTime() {
563:
564: if (!lifespanSet || eternal
565: || (timeToLive == 0 && timeToIdle == 0)) {
566: return Long.MAX_VALUE;
567: }
568:
569: long expirationTime = 0;
570: long ttlExpiry = creationTime + timeToLive * ONE_SECOND;
571:
572: long mostRecentTime = Math.max(creationTime,
573: nextToLastAccessTime);
574: long ttiExpiry = mostRecentTime + timeToIdle * ONE_SECOND;
575:
576: if (timeToLive != 0 && (timeToIdle == 0 || lastAccessTime == 0)) {
577: expirationTime = ttlExpiry;
578: } else if (timeToLive == 0) {
579: expirationTime = ttiExpiry;
580: } else {
581: expirationTime = Math.min(ttlExpiry, ttiExpiry);
582: }
583: return expirationTime;
584: }
585:
586: /**
587: * @return true if the element is eternal
588: */
589: public boolean isEternal() {
590: return eternal;
591: }
592:
593: /**
594: * Sets whether the element is eternal.
595: *
596: * @param eternal
597: */
598: public void setEternal(boolean eternal) {
599: this .eternal = eternal;
600: lifespanSet = true;
601: }
602:
603: /**
604: * Whether any combination of eternal, TTL or TTI has been set.
605: *
606: * @return true if set.
607: */
608: public boolean isLifespanSet() {
609: return lifespanSet;
610: }
611:
612: /**
613: * @return the time to live, in seconds
614: */
615: public int getTimeToLive() {
616: return timeToLive;
617: }
618:
619: /**
620: * @return the time to idle, in seconds
621: */
622: public int getTimeToIdle() {
623: return timeToIdle;
624: }
625: }
|