All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.ehcache.Element Maven / Gradle / Ivy

/**
 *  Copyright 2003-2009 Terracotta, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package net.sf.ehcache;


import net.sf.ehcache.util.TimeUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A Cache Element, consisting of a key, value and attributes.
 * 

* From ehcache-1.2, Elements can have keys and values that are Serializable or Objects. To preserve backward * compatibility, special accessor methods for Object keys and values are provided: {@link #getObjectKey()} and * {@link #getObjectValue()}. If placing Objects in ehcache, developers must use the new getObject... methods to * avoid CacheExceptions. The get... methods are reserved for Serializable keys and values. * * @author Greg Luck * @version $Id: Element.java 1406 2009-11-06 11:43:02Z gluck $ */ public class Element implements Serializable, Cloneable { /** * serial version * Updated for version 1.2, 1.2.1 and 1.7 */ private static final long serialVersionUID = 1098572221246444544L; private static final Logger LOG = LoggerFactory.getLogger(Element.class.getName()); private static final AtomicLongFieldUpdater HIT_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(Element.class, "hitCount"); /** * the cache key. */ private final Object key; /** * the value. */ private final Object value; /** * version of the element. System.currentTimeMillis() is used to compute version for updated elements. That * way, the actual version of the updated element does not need to be checked. */ private volatile long version; /** * The number of times the element was hit. */ private volatile long hitCount; /** * The amount of time for the element to live, in seconds. 0 indicates unlimited. */ private volatile int timeToLive = Integer.MIN_VALUE; /** * The amount of time for the element to idle, in seconds. 0 indicates unlimited. */ private volatile int timeToIdle = Integer.MIN_VALUE; /** * Pluggable element eviction data instance */ private volatile transient ElementEvictionData elementEvictionData; /** * If there is an Element in the Cache and it is replaced with a new Element for the same key, * then both the version number and lastUpdateTime should be updated to reflect that. The creation time * will be the creation time of the new Element, not the original one, so that TTL concepts still work. */ private volatile long lastUpdateTime; /** * A full constructor. *

* Creation time is set to the current time. Last Access Time is not set. * * @since .4 */ public Element(final Serializable key, final Serializable value, final long version) { this((Object) key, (Object) value, version); } /** * A full constructor. *

* Creation time is set to the current time. Last Access Time and Previous To Last Access Time * are not set. * * @since 1.2 */ public Element(final Object key, final Object value, final long version) { this.key = key; this.value = value; this.version = version; HIT_COUNT_UPDATER.set(this, 0); this.elementEvictionData = new DefaultElementEvictionData(TimeUtil.toSecs(System.currentTimeMillis())); } /** * Constructor. * * @deprecated The {@code nextToLastAccessTime} field is unused since * version 1.7, setting it will have no effect. Use * #Element(Object, Object, long, long, long, long, long) * instead * @since 1.3 * @see #Element(Object, Object, long, long, long, long, long) */ @Deprecated public Element(final Object key, final Object value, final long version, final long creationTime, final long lastAccessTime, final long nextToLastAccessTime, final long lastUpdateTime, final long hitCount) { this(key, value, version, creationTime, lastAccessTime, lastUpdateTime, hitCount); } /** * Constructor. * * @since 1.7 */ public Element(final Object key, final Object value, final long version, final long creationTime, final long lastAccessTime, final long lastUpdateTime, final long hitCount) { this.key = key; this.value = value; this.version = version; this.lastUpdateTime = lastUpdateTime; HIT_COUNT_UPDATER.set(this, hitCount); this.elementEvictionData = new DefaultElementEvictionData(TimeUtil.toSecs(creationTime), TimeUtil.toSecs(lastAccessTime)); } /** * Constructor used by ElementData. Needs to be public since ElementData might be in another classloader * * @since 1.7 */ public Element(final Object key, final Object value, final long version, final long creationTime, final long lastAccessTime, final long hitCount, final int timeToLive, final int timeToIdle, final long lastUpdateTime) { this.key = key; this.value = value; this.version = version; HIT_COUNT_UPDATER.set(this, hitCount); setTimeToLive(timeToLive); setTimeToIdle(timeToIdle); this.lastUpdateTime = lastUpdateTime; this.elementEvictionData = new DefaultElementEvictionData(TimeUtil.toSecs(creationTime), TimeUtil.toSecs(lastAccessTime)); } /** * Constructor used by ehcache-server * * @param key any non null value * @param value any value, including nulls * @param eternal specify as non-null to override cache configuration * @param timeToIdleSeconds specify as non-null to override cache configuration * @param timeToLiveSeconds specify as non-null to override cache configuration */ public Element(final Object key, final Object value, final Boolean eternal, final Integer timeToIdleSeconds, final Integer timeToLiveSeconds) { this.key = key; this.value = value; if (eternal != null) { setEternal(eternal.booleanValue()); } if (timeToIdleSeconds != null) { setTimeToIdle(timeToIdleSeconds.intValue()); } if (timeToLiveSeconds != null) { setTimeToLive(timeToLiveSeconds.intValue()); } this.elementEvictionData = new DefaultElementEvictionData(TimeUtil.toSecs(System.currentTimeMillis())); } /** * Constructor. * * @param key * @param value */ public Element(final Serializable key, final Serializable value) { this((Object) key, (Object) value, 1L); } /** * Constructor. * * @param key * @param value * @since 1.2 */ public Element(final Object key, final Object value) { this(key, value, 1L); } /** * Gets the key attribute of the Element object. * * @return The key value. If the key is not Serializable, null is returned and an info log message emitted * @see #getObjectKey() */ public final Serializable getKey() { Serializable keyAsSerializable; try { keyAsSerializable = (Serializable) key; } catch (Exception e) { throw new CacheException("The key " + key + " is not Serializable. Consider using Element#getObjectKey()"); } return keyAsSerializable; } /** * Gets the key attribute of the Element object. *

* This method is provided for those wishing to use ehcache as a memory only cache * and enables retrieval of non-Serializable values from elements. * * @return The key as an Object. i.e no restriction is placed on it * @see #getKey() */ public final Object getObjectKey() { return key; } /** * Gets the value attribute of the Element object. * * @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 * @see #getObjectValue() */ public final Serializable getValue() { Serializable valueAsSerializable; try { valueAsSerializable = (Serializable) value; } catch (Exception e) { throw new CacheException("The value " + value + " for key " + key + " is not Serializable. Consider using Element#getObjectKey()"); } return valueAsSerializable; } /** * Gets the value attribute of the Element object as an Object. *

* This method is provided for those wishing to use ehcache as a memory only cache * and enables retrieval of non-Serializable values from elements. * * @return The value as an Object. i.e no restriction is placed on it * @see #getValue() * @since 1.2 */ public final Object getObjectValue() { return value; } /** * Equals comparison with another element, based on the key. */ @Override public final boolean equals(final Object object) { if (object == null || !(object instanceof Element)) { return false; } Element element = (Element) object; if (key == null || element.getObjectKey() == null) { return false; } return key.equals(element.getObjectKey()); } /** * Sets time to Live * * @param timeToLiveSeconds the number of seconds to live */ public void setTimeToLive(final int timeToLiveSeconds) { if (timeToLiveSeconds < 0) { throw new IllegalArgumentException("timeToLive can't be negative"); } this.timeToLive = timeToLiveSeconds; } /** * Sets time to idle * * @param timeToIdleSeconds the number of seconds to idle */ public void setTimeToIdle(final int timeToIdleSeconds) { if (timeToIdleSeconds < 0) { throw new IllegalArgumentException("timeToIdle can't be negative"); } this.timeToIdle = timeToIdleSeconds; } /** * Gets the hashcode, based on the key. */ @Override public final int hashCode() { return key.hashCode(); } /** * Sets the version attribute of the ElementAttributes object. * * @param version The new version value */ public final void setVersion(final long version) { this.version = version; } /** * Sets the creationTime attribute of the ElementAttributes object. *

* Note that in a Terracotta clustered environment, resetting the creation * time will not have any effect. * * @deprecated Resetting the creation time is not recommended as of version * 1.7 */ @Deprecated public final void setCreateTime() { this.elementEvictionData.setCreationTime(TimeUtil.toSecs(System.currentTimeMillis())); } /** * Gets the creationTime of the Element * * @return The creationTime value */ public final long getCreationTime() { return TimeUtil.toMillis(elementEvictionData.getCreationTime()); } /** * Calculates the latest of creation and update time * @return if never updated, creation time is returned, otherwise updated time */ public final long getLatestOfCreationAndUpdateTime() { if (0 == lastUpdateTime) { return TimeUtil.toMillis(elementEvictionData.getCreationTime()); } else { return lastUpdateTime; } } /** * Gets the version attribute of the ElementAttributes object. * * @return The version value */ public final long getVersion() { return version; } /** * Gets the last access time. * Access means a get. So a newly created {@link Element} * will have a last access time equal to its create time. */ public final long getLastAccessTime() { return TimeUtil.toMillis(elementEvictionData.getLastAccessTime()); } /** * Gets the next to last access time. * * @deprecated The {@code nextToLastAccessTime} field is unused since * version 1.7, retrieving it will return the {@code * lastAccessTime}. Use #getLastAccessTime() instead. * @see #getLastAccessTime() */ @Deprecated public final long getNextToLastAccessTime() { return getLastAccessTime(); } /** * Gets the hit count on this element. */ public final long getHitCount() { return hitCount; } /** * Retrieves this element's eviction data instance. * * @return this element's eviction data instance */ public ElementEvictionData getElementEvictionData() { return elementEvictionData; } /** * Sets this element's eviction data instance. * * @param elementEvictionData this element's eviction data */ public void setElementEvictionData(ElementEvictionData elementEvictionData) { this.elementEvictionData = elementEvictionData; } /** * Resets the hit count to 0 and the last access time to now. Used when an Element is put into a cache. */ public final void resetAccessStatistics() { elementEvictionData.resetLastAccessTime(this); HIT_COUNT_UPDATER.set(this, 0); } /** * Sets the last access time to now and increase the hit count. */ public final void updateAccessStatistics() { elementEvictionData.updateLastAccessTime(TimeUtil.toSecs(System.currentTimeMillis()), this); HIT_COUNT_UPDATER.incrementAndGet(this); } /** * Sets the last access time to now without updating the hit count. */ public final void updateUpdateStatistics() { lastUpdateTime = System.currentTimeMillis(); version = lastUpdateTime; } /** * Returns a {@link String} representation of the {@link Element}. */ @Override public final String toString() { StringBuffer sb = new StringBuffer(); sb.append("[ key = ").append(key) .append(", value=").append(value) .append(", version=").append(version) .append(", hitCount=").append(hitCount) .append(", CreationTime = ").append(this.getCreationTime()) .append(", LastAccessTime = ").append(this.getLastAccessTime()) .append(" ]"); return sb.toString(); } /** * Clones an Element. A completely new object is created, with no common references with the * existing one. *

* This method will not work unless the Object is Serializable *

* Warning: This can be very slow on large object graphs. If you use this method * you should write a performance test to verify suitability. * * @return a new {@link Element}, with exactly the same field values as the one it was cloned from. * @throws CloneNotSupportedException */ @Override public final Object clone() throws CloneNotSupportedException { //Not used. Just to get code inspectors to shut up super.clone(); try { Element element = new Element(deepCopy(key), deepCopy(value), version); element.elementEvictionData = elementEvictionData.clone(); HIT_COUNT_UPDATER.set(element, hitCount); return element; } catch (IOException e) { LOG.error("Error cloning Element with key " + key + " during serialization and deserialization of value"); throw new CloneNotSupportedException(); } catch (ClassNotFoundException e) { LOG.error("Error cloning Element with key " + key + " during serialization and deserialization of value"); throw new CloneNotSupportedException(); } } private static Object deepCopy(final Object oldValue) throws IOException, ClassNotFoundException { Serializable newValue = null; ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = null; ObjectInputStream ois = null; try { oos = new ObjectOutputStream(bout); oos.writeObject(oldValue); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ois = new ObjectInputStream(bin); newValue = (Serializable) ois.readObject(); } finally { try { if (oos != null) { oos.close(); } if (ois != null) { ois.close(); } } catch (Exception e) { LOG.error("Error closing Stream"); } } return newValue; } /** * The size of this object in serialized form. This is not the same * thing as the memory size, which is JVM dependent. Relative values should be meaningful, * however. *

* Warning: This method can be very slow for values which contain large object graphs. *

* If the key or value of the Element is not Serializable, an error will be logged and 0 will be returned. * * @return The serialized size in bytes */ public final long getSerializedSize() { if (!isSerializable()) { return 0; } long size = 0; ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(bout); oos.writeObject(this); size = bout.size(); return size; } catch (IOException e) { LOG.debug("Error measuring element size for element with key " + key + ". Cause was: " + e.getMessage()); } finally { try { if (oos != null) { oos.close(); } } catch (Exception e) { LOG.error("Error closing ObjectOutputStream"); } } return size; } /** * Whether the element may be Serialized. *

* While Element implements Serializable, it is possible to create non Serializable elements * for use in MemoryStores. This method checks that an instance of Element really is Serializable * and will not throw a NonSerializableException if Serialized. *

* This method was tweaked in 1.6 as it has been shown that Serializable classes can be serializaed as can * null, regardless of what class it is a null of. ObjectOutputStream.write(null) works and ObjectInputStream.read() * will read null back. * * @return true if the element is Serializable * @since 1.2 */ public final boolean isSerializable() { return isKeySerializable() && (value instanceof Serializable || value == null) && elementEvictionData.canParticipateInSerialization(); } /** * Whether the element's key may be Serialized. *

* While Element implements Serializable, it is possible to create non Serializable elements and/or * non Serializable keys for use in MemoryStores. *

* This method checks that an instance of an Element's key really is Serializable * and will not throw a NonSerializableException if Serialized. * * @return true if the element's key is Serializable * @since 1.2 */ public final boolean isKeySerializable() { return key instanceof Serializable || key == null; } /** * If there is an Element in the Cache and it is replaced with a new Element for the same key, * then both the version number and lastUpdateTime should be updated to reflect that. The creation time * will be the creation time of the new Element, not the original one, so that TTL concepts still work. * * @return the time when the last update occured. If this is the original Element, the time will be null */ public long getLastUpdateTime() { return lastUpdateTime; } /** * An element is expired if the expiration time as given by {@link #getExpirationTime()} is in the past. * * @return true if the Element is expired, otherwise false. If no lifespan has been set for the Element it is * considered not able to expire. * @see #getExpirationTime() */ public boolean isExpired() { if (!isLifespanSet()) { return false; } long now = System.currentTimeMillis(); long expirationTime = getExpirationTime(); return now > expirationTime; } /** * Returns the expiration time based on time to live. If this element also has a time to idle setting, the expiry * time will vary depending on whether the element is accessed. * * @return the time to expiration */ public long getExpirationTime() { if (!isLifespanSet() || isEternal()) { return Long.MAX_VALUE; } long expirationTime = 0; long ttlExpiry = TimeUtil.toMillis(elementEvictionData.getCreationTime()) + TimeUtil.toMillis(getTimeToLive()); long mostRecentTime = Math.max(TimeUtil.toMillis(elementEvictionData.getCreationTime()), TimeUtil.toMillis(elementEvictionData.getLastAccessTime())); long ttiExpiry = mostRecentTime + TimeUtil.toMillis(getTimeToIdle()); if (getTimeToLive() != 0 && (getTimeToIdle() == 0 || elementEvictionData.getLastAccessTime() == 0)) { expirationTime = ttlExpiry; } else if (getTimeToLive() == 0) { expirationTime = ttiExpiry; } else { expirationTime = Math.min(ttlExpiry, ttiExpiry); } return expirationTime; } /** * @return true if the element is eternal */ public boolean isEternal() { return 0 == timeToIdle && 0 == timeToLive; } /** * Sets whether the element is eternal. * * @param eternal */ public void setEternal(final boolean eternal) { if (eternal) { this.timeToIdle = 0; this.timeToLive = 0; } else if (isEternal()) { this.timeToIdle = Integer.MIN_VALUE; this.timeToLive = Integer.MIN_VALUE; } } /** * Whether any combination of eternal, TTL or TTI has been set. * * @return true if set. */ public boolean isLifespanSet() { return this.timeToIdle != Integer.MIN_VALUE || this.timeToLive != Integer.MIN_VALUE; } /** * @return the time to live, in seconds */ public int getTimeToLive() { if (Integer.MIN_VALUE == timeToLive) { return 0; } return timeToLive; } /** * @return the time to idle, in seconds */ public int getTimeToIdle() { if (Integer.MIN_VALUE == timeToIdle) { return 0; } return timeToIdle; } /** * Custom serialization write logic */ private void writeObject(ObjectOutputStream out) throws IOException { if (!elementEvictionData.canParticipateInSerialization()) { throw new NotSerializableException(); } out.defaultWriteObject(); out.writeInt(elementEvictionData.getCreationTime()); out.writeInt(elementEvictionData.getLastAccessTime()); } /** * Custom serialization read logic */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); elementEvictionData = new DefaultElementEvictionData(in.readInt(), in.readInt()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy