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