View Javadoc

1   /**
2    *  Copyright 2003-2007 Greg Luck
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  
17  
18  package net.sf.ehcache;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.ObjectInputStream;
27  import java.io.ObjectOutputStream;
28  import java.io.Serializable;
29  
30  /**
31   * A Cache Element, consisting of a key, value and attributes.
32   * <p/>
33   * From ehcache-1.2, Elements can have keys and values that are Serializable or Objects. To preserve backward
34   * compatibility, special accessor methods for Object keys and values are provided: {@link #getObjectKey()} and
35   * {@link #getObjectValue()}. If placing Objects in ehcache, developers must use the new getObject... methods to
36   * avoid CacheExceptions. The get... methods are reserved for Serializable keys and values.
37   *
38   * @author Greg Luck
39   * @version $Id: Element.java 512 2007-07-10 09:18:45Z gregluck $
40   * @noinspection SerializableHasSerializationMethods
41   */
42  public final class Element implements Serializable, Cloneable {
43      /**
44       * serial version
45       * Updated for version 1.2 and again for 1.2.1
46       */
47      private static final long serialVersionUID = 3343087714201120157L;
48  
49      private static final Log LOG = LogFactory.getLog(Element.class.getName());
50  
51      private static final long ONE_SECOND = 1000L;
52  
53      /**
54       * the cache key.
55       */
56      private final Object key;
57  
58      /**
59       * the value.
60       */
61      private Object value;
62  
63      /**
64       * version of the element. System.currentTimeMillis() is used to compute version for updated elements. That
65       * way, the actual version of the updated element does not need to be checked.
66       */
67      private long version;
68  
69      /**
70       * The creation time.
71       */
72      private long creationTime;
73  
74      /**
75       * The last access time.
76       */
77      private long lastAccessTime;
78  
79      /**
80       * The next to last access time. Used by the expiry mechanism
81       */
82      private long nextToLastAccessTime;
83  
84      /**
85       * The number of times the element was hit.
86       */
87      private long hitCount;
88  
89      /**
90       * The amount of time for the element to live, in seconds. 0 indicates unlimited.
91       */
92      private int timeToLive;
93  
94      /**
95       * The amount of time for the element to idle, in seconds. 0 indicates unlimited.
96       */
97      private int timeToIdle;
98  
99      /**
100      * If there is an Element in the Cache and it is replaced with a new Element for the same key,
101      * then both the version number and lastUpdateTime should be updated to reflect that. The creation time
102      * will be the creation time of the new Element, not the original one, so that TTL concepts still work.
103      */
104     private long lastUpdateTime;
105 
106     /**
107      * Whether the element is eternal, i.e. never expires.
108      */
109     private boolean eternal;
110 
111 
112     /**
113      * Whether any combination of eternal, TTL or TTI has been set.
114      */
115     private boolean lifespanSet;
116 
117 
118 
119     /**
120      * A full constructor.
121      * <p/>
122      * Creation time is set to the current time. Last Access Time and Previous To Last Access Time
123      * are not set.
124      *
125      * @since .4
126      */
127     public Element(Serializable key, Serializable value, long version) {
128         this((Object) key, (Object) value, version);
129 
130     }
131 
132     /**
133      * A full constructor.
134      * <p/>
135      * Creation time is set to the current time. Last Access Time and Previous To Last Access Time
136      * are not set.
137      *
138      * @since 1.2
139      */
140     public Element(Object key, Object value, long version) {
141         this.key = key;
142         this.value = value;
143         this.version = version;
144         creationTime = System.currentTimeMillis();
145         hitCount = 0;
146     }
147 
148     /**
149      * A full constructor.
150      *
151      * @since 1.3
152      */
153     public Element(Object key, Object value, long version, 
154             long creationTime, long lastAccessTime, 
155             long nextToLastAccessTime, long lastUpdateTime, 
156             long hitCount) {
157         this.key = key;
158         this.value = value;
159         this.version = version;
160         this.creationTime = creationTime;
161         this.lastAccessTime = lastAccessTime;
162         this.nextToLastAccessTime = nextToLastAccessTime;
163         this.lastUpdateTime = lastUpdateTime;
164         this.hitCount = hitCount;
165     }
166 
167     /**
168      * Constructor.
169      *
170      * @param key
171      * @param value
172      */
173     public Element(Serializable key, Serializable value) {
174         this((Object) key, (Object) value, 1L);
175     }
176 
177     /**
178      * Constructor.
179      *
180      * @param key
181      * @param value
182      * @since 1.2
183      */
184     public Element(Object key, Object value) {
185         this(key, value, 1L);
186     }
187 
188     /**
189      * Gets the key attribute of the Element object.
190      *
191      * @return The key value. If the key is not Serializable, null is returned and an info log message emitted
192      * @see #getObjectKey()
193      */
194     public final Serializable getKey() {
195         Serializable keyAsSerializable;
196         try {
197             keyAsSerializable = (Serializable) key;
198         } catch (Exception e) {
199             throw new CacheException("Key " + key + " is not Serializable. Consider using Element#getObjectKey()");
200         }
201         return keyAsSerializable;
202     }
203 
204     /**
205      * Gets the key attribute of the Element object.
206      * <p/>
207      * This method is provided for those wishing to use ehcache as a memory only cache
208      * and enables retrieval of non-Serializable values from elements.
209      *
210      * @return The key as an Object. i.e no restriction is placed on it
211      * @see #getKey()
212      */
213     public final Object getObjectKey() {
214         return key;
215     }
216 
217     /**
218      * Gets the value attribute of the Element object.
219      *
220      * @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
221      * @see #getObjectValue()
222      */
223     public final Serializable getValue() {
224         Serializable valueAsSerializable;
225         try {
226             valueAsSerializable = (Serializable) value;
227         } catch (Exception e) {
228             throw new CacheException("Value " + value + " is not Serializable. Consider using Element#getObjectKey()");
229         }
230         return valueAsSerializable;
231     }
232 
233     /**
234      * Gets the value attribute of the Element object as an Object.
235      * <p/>
236      * This method is provided for those wishing to use ehcache as a memory only cache
237      * and enables retrieval of non-Serializable values from elements.
238      *
239      * @return The value as an Object.  i.e no restriction is placed on it
240      * @see #getValue()
241      * @since 1.2
242      */
243     public final Object getObjectValue() {
244         return value;
245     }
246 
247     /**
248      * Equals comparison with another element, based on the key.
249      */
250     public final boolean equals(Object object) {
251         if (object == null || !(object instanceof Element)) {
252             return false;
253         }
254 
255         Element element = (Element) object;
256         if (key == null || element.getObjectKey() == null) {
257             return false;
258         }
259 
260         return key.equals(element.getObjectKey());
261     }
262 
263     /**
264      * Sets time to Live
265      *
266      * @param timeToLiveSeconds the number of seconds to live
267      */
268     public void setTimeToLive(int timeToLiveSeconds) {
269         this.timeToLive = timeToLiveSeconds;
270         lifespanSet = true;
271     }
272 
273     /**
274      * Sets time to idle
275      *
276      * @param timeToIdleSeconds the number of seconds to idle
277      */
278     public void setTimeToIdle(int timeToIdleSeconds) {
279         this.timeToIdle = timeToIdleSeconds;
280         lifespanSet = true;
281     }
282 
283     /**
284      * Gets the hascode, based on the key.
285      */
286     public final int hashCode() {
287         return key.hashCode();
288     }
289 
290     /**
291      * Sets the version attribute of the ElementAttributes object.
292      *
293      * @param version The new version value
294      */
295     public final void setVersion(long version) {
296         this.version = version;
297     }
298 
299     /**
300      * Gets the creationTime attribute of the ElementAttributes object.
301      *
302      * @return The creationTime value
303      */
304     public final long getCreationTime() {
305         return creationTime;
306     }
307 
308     /**
309      * Sets the creationTime attribute of the ElementAttributes object.
310      */
311     public final void setCreateTime() {
312         creationTime = System.currentTimeMillis();
313     }
314 
315     /**
316      * Gets the version attribute of the ElementAttributes object.
317      *
318      * @return The version value
319      */
320     public final long getVersion() {
321         return version;
322     }
323 
324     /**
325      * Gets the last access time.
326      * Access means a get. So a newly created {@link Element}
327      * will have a last access time equal to its create time.
328      */
329     public final long getLastAccessTime() {
330         return lastAccessTime;
331     }
332 
333     /**
334      * Gets the next to last access time.
335      *
336      * @see #getLastAccessTime()
337      */
338     public final long getNextToLastAccessTime() {
339         return nextToLastAccessTime;
340     }
341 
342     /**
343      * Gets the hit count on this element.
344      */
345     public final long getHitCount() {
346         return hitCount;
347     }
348 
349     /**
350      * Resets the hit count to 0 and the last access time to 0.
351      */
352     public final void resetAccessStatistics() {
353         lastAccessTime = 0;
354         nextToLastAccessTime = 0;
355         hitCount = 0;
356     }
357 
358     /**
359      * Sets the last access time to now.
360      */
361     public final void updateAccessStatistics() {
362         nextToLastAccessTime = lastAccessTime;
363         lastAccessTime = System.currentTimeMillis();
364         hitCount++;
365     }
366 
367     /**
368      * Sets the last access time to now.
369      */
370     public final void updateUpdateStatistics() {
371         lastUpdateTime = System.currentTimeMillis();
372         version = lastUpdateTime;
373     }
374 
375 
376     /**
377      * Returns a {@link String} representation of the {@link Element}.
378      */
379     public final String toString() {
380         StringBuffer sb = new StringBuffer();
381 
382         sb.append("[ key = ").append(key)
383                 .append(", value=").append(value)
384                 .append(", version=").append(version)
385                 .append(", hitCount=").append(hitCount)
386                 .append(", CreationTime = ").append(this.getCreationTime())
387                 .append(", LastAccessTime = ").append(this.getLastAccessTime())
388                 .append(" ]");
389 
390         return sb.toString();
391     }
392 
393     /**
394      * Clones an Element. A completely new object is created, with no common references with the
395      * existing one.
396      * <p/>
397      * This method will not work unless the Object is Serializable
398      * <p/>
399      * Warning: This can be very slow on large object graphs. If you use this method
400      * you should write a performance test to verify suitability.
401      *
402      * @return a new {@link Element}, with exactly the same field values as the one it was cloned from.
403      * @throws CloneNotSupportedException
404      */
405     public final Object clone() throws CloneNotSupportedException {
406         //Not used. Just to get code inspectors to shut up
407         super.clone();
408 
409         Element element = new Element(deepCopy(key), deepCopy(value), version);
410         element.creationTime = creationTime;
411         element.lastAccessTime = lastAccessTime;
412         element.nextToLastAccessTime = nextToLastAccessTime;
413         element.hitCount = hitCount;
414         return element;
415     }
416 
417     private Object deepCopy(Object oldValue) {
418         Serializable newValue = null;
419         ByteArrayOutputStream bout = new ByteArrayOutputStream();
420         ObjectOutputStream oos = null;
421         ObjectInputStream ois = null;
422         try {
423             oos = new ObjectOutputStream(bout);
424             oos.writeObject(oldValue);
425             ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
426             ois = new ObjectInputStream(bin);
427             newValue = (Serializable) ois.readObject();
428         } catch (IOException e) {
429             LOG.error("Error cloning Element with key " + key
430                     + " during serialization and deserialization of value");
431         } catch (ClassNotFoundException e) {
432             LOG.error("Error cloning Element with key " + key
433                     + " during serialization and deserialization of value");
434         } finally {
435             try {
436                 if (oos != null) {
437                     oos.close();
438                 }
439                 if (ois != null) {
440                     ois.close();
441                 }
442             } catch (Exception e) {
443                 LOG.error("Error closing Stream");
444             }
445         }
446         return newValue;
447     }
448 
449     /**
450      * The size of this object in serialized form. This is not the same
451      * thing as the memory size, which is JVM dependent. Relative values should be meaningful,
452      * however.
453      * <p/>
454      * Warning: This method can be <b>very slow</b> for values which contain large object graphs.
455      * <p/>
456      * If the key or value of the Element is not Serializable, an error will be logged and 0 will be returned.
457      * @return The serialized size in bytes
458      */
459     public final long getSerializedSize() {
460 
461         if (!isSerializable()) {
462             return 0;
463         }
464         long size = 0;
465         ByteArrayOutputStream bout = new ByteArrayOutputStream();
466         ObjectOutputStream oos = null;
467         try {
468             oos = new ObjectOutputStream(bout);
469             oos.writeObject(this);
470             size = bout.size();
471             return size;
472         } catch (IOException e) {
473             LOG.debug("Error measuring element size for element with key " + key + ". Cause was: " + e.getMessage());
474         } finally {
475             try {
476                 if (oos != null) {
477                     oos.close();
478                 }
479             } catch (Exception e) {
480                 LOG.error("Error closing ObjectOutputStream");
481             }
482         }
483 
484         return size;
485     }
486 
487     /**
488      * Whether the element may be Serialized.
489      * <p/>
490      * While Element implements Serializable, it is possible to create non Serializable elements
491      * for use in MemoryStores. This method checks that an instance of Element really is Serializable
492      * and will not throw a NonSerializableException if Serialized.
493      *
494      * @return true if the element is Serializable
495      * @since 1.2
496      */
497     public final boolean isSerializable() {
498         return key instanceof Serializable && value instanceof Serializable;
499     }
500 
501     /**
502      * Whether the element's key may be Serialized.
503      * <p/>
504      * While Element implements Serializable, it is possible to create non Serializable elements and/or
505      * non Serializable keys for use in MemoryStores.
506      * <p/>
507      * This method checks that an instance of an Element's key really is Serializable
508      * and will not throw a NonSerializableException if Serialized.
509      *
510      * @return true if the element's key is Serializable
511      * @since 1.2
512      */
513     public final boolean isKeySerializable() {
514         return key instanceof Serializable;
515     }
516 
517     /**
518      * If there is an Element in the Cache and it is replaced with a new Element for the same key,
519      * then both the version number and lastUpdateTime should be updated to reflect that. The creation time
520      * will be the creation time of the new Element, not the original one, so that TTL concepts still work.
521      *
522      * @return the time when the last update occured. If this is the original Element, the time will be null
523      */
524     public long getLastUpdateTime() {
525         return lastUpdateTime;
526     }
527 
528     /**
529      * An element is expired if the expiration time as given by {@link #getExpirationTime()} is in the past.
530      *
531      * @return true if the Element is expired, otherwise false. If no lifespan has been set for the Element it is
532      *         considered not able to expire.
533      * @see #getExpirationTime()
534      */
535     public boolean isExpired() {
536         if (!lifespanSet) {
537             return false;
538         }
539 
540         long now = System.currentTimeMillis();
541         long expirationTime = getExpirationTime();
542 
543         return now > expirationTime;
544     }
545 
546     /**
547      * Returns the expiration time based on time to live. If this element also has a time to idle setting, the expiry
548      * time will vary depending on whether the element is accessed.
549      *
550      * @return the time to expiration
551      */
552     public long getExpirationTime() {
553 
554         if (!lifespanSet || eternal || (timeToLive == 0 && timeToIdle == 0)) {
555             return Long.MAX_VALUE;
556         }
557 
558         long expirationTime = 0;
559         long ttlExpiry = creationTime + timeToLive * ONE_SECOND;
560 
561         long mostRecentTime = Math.max(creationTime, nextToLastAccessTime);
562         long ttiExpiry = mostRecentTime + timeToIdle * ONE_SECOND;
563 
564         if (timeToLive != 0 && (timeToIdle == 0 || lastAccessTime == 0)) {
565             expirationTime = ttlExpiry;
566         } else if (timeToLive == 0) {
567             expirationTime = ttiExpiry;
568         } else {
569             expirationTime = Math.min(ttlExpiry, ttiExpiry);
570         }
571         return expirationTime;
572     }
573 
574     /**
575      * @return true if the element is eternal
576      */
577     public boolean isEternal() {
578         return eternal;
579     }
580 
581     /**
582      * Sets whether the element is eternal.
583      *
584      * @param eternal
585      */
586     public void setEternal(boolean eternal) {
587         this.eternal = eternal;
588         lifespanSet = true;
589     }
590 
591     /**
592      * Whether any combination of eternal, TTL or TTI has been set.
593      *
594      * @return true if set.
595      */
596     public boolean isLifespanSet() {
597         return lifespanSet;
598     }
599 
600     /**
601      * @return the time to live, in seconds
602      */
603     public int getTimeToLive() {
604         return timeToLive;
605     }
606 
607     /**
608      * @return the time to idle, in seconds
609      */
610     public int getTimeToIdle() {
611         return timeToIdle;
612     }
613 }
614 
615 
616