/*
* Created on Apr 5, 2006
*
* 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.
*
* Copyright @2006 the original author or authors.
*/
package org.springmodules.cache.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springmodules.util.Objects;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* <p>
* A cache element that stores <em>copies</em> of the given key and value.
* </p>
*
* @author Omar Irbouh
* @author Alex Ruiz
*/
public class Element implements Serializable, Cloneable {
private static final long DEFAULT_TIME_TO_LIVE_MS = 120000;
private static final long EXPIRY_NEVER = -1l;
private static Log logger = LogFactory.getLog(Element.class);
private static final long serialVersionUID = -935757449385127201L;
private final long creationTime;
private final Serializable key;
private final long timeToLive;
private Serializable value;
/**
* Constructor. Entries created with this constructor never expire.
*
* <p>
* The key and value stored in this element are copies of the ones passed as
* arguments.
* </p>
*
* @param newKey
* the new key for this entry
* @param newValue
* the new value for this entry
* @throws ObjectCannotBeCopiedException
* if the key or the value cannot be copied using serialization
*/
public Element(Serializable newKey, Serializable newValue)
throws ObjectCannotBeCopiedException {
this(newKey, newValue, EXPIRY_NEVER);
}
/**
* Constructor.
*
* <p>
* The key and value stored in this element are copies of the ones passed as
* arguments.
* </p>
*
* @param newKey
* the new key for this entry
* @param newValue
* the new value for this entry
* @param newTimeToLive
* the number of milliseconds until the cache entry will expire
* @throws ObjectCannotBeCopiedException
* if the key or the value cannot be copied using serialization
*/
public Element(Serializable newKey, Serializable newValue, long newTimeToLive)
throws ObjectCannotBeCopiedException {
this(newKey, newValue, System.currentTimeMillis(), newTimeToLive);
}
private Element(Serializable newKey, Serializable newValue,
long newCreationTime, long newTimeToLive)
throws ObjectCannotBeCopiedException {
super();
key = copy(newKey);
setValue(newValue);
creationTime = newCreationTime;
boolean invalidTimeToLive = newTimeToLive <= 0
&& newTimeToLive != EXPIRY_NEVER;
timeToLive = invalidTimeToLive ? DEFAULT_TIME_TO_LIVE_MS : newTimeToLive;
}
/**
* @see Object#clone()
*/
public Object clone() {
Element newElement = new Element(key, value, creationTime, timeToLive);
return newElement;
}
/**
* @see Object#equals(Object)
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Element)) {
return false;
}
Element other = (Element) obj;
if (!ObjectUtils.nullSafeEquals(key, other.key)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(value, other.value)) {
return false;
}
return true;
}
/**
* @return the creation time (in milliseconds) of this cache element
*/
public final long getCreationTime() {
return creationTime;
}
/**
* @return the key of this cache element
*/
public final Serializable getKey() {
return key;
}
/**
* @return the number of milliseconds until the cache entry will expire
*/
public final long getTimeToLive() {
return timeToLive;
}
/**
* @return the value of this cache element
*/
public final Serializable getValue() {
return value;
}
/**
* @see Object#hashCode()
*/
public int hashCode() {
int multiplier = 31;
int hash = 7;
hash = multiplier * hash + key.hashCode();
hash = multiplier * hash + Objects.nullSafeHashCode(value);
return hash;
}
/**
* @return <code>true</code> if this cache element has not expired yet. This
* method always returns <code>true</code> for eternal entries.
* @see #Element(Serializable, Serializable)
*/
public final boolean isAlive() {
if (timeToLive == EXPIRY_NEVER) {
return true;
}
long currentTime = System.currentTimeMillis();
long delta = currentTime - creationTime;
return delta < timeToLive;
}
/**
* @return <code>true</code> if this cache element has expired
* @see #isAlive()
*/
public final boolean isExpired() {
return !isAlive();
}
/**
* Sets the value for this cache element.
*
* @param newValue
* the new value for this cache element
* @throws ObjectCannotBeCopiedException
* if the key or the value cannot be copied using serialization
*/
public final void setValue(Serializable newValue)
throws ObjectCannotBeCopiedException {
value = copy(newValue);
}
/**
* @see Object#toString()
*/
public String toString() {
return Objects.identityToString(this)
.append("[key=").append(StringUtils.quoteIfString(key)).append(", ")
.append("value=").append(StringUtils.quoteIfString(value)).append(", ")
.append("creationTime=").append(new Date(creationTime)).append(", ")
.append("timeToLive=").append(timeToLive).append("]")
.toString();
}
private void close(InputStream closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (Exception exception) {
String clazz = closeable.getClass().getName();
logger.error("Unable to close " + clazz, exception);
}
}
private void close(OutputStream closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (Exception exception) {
String clazz = closeable.getClass().getName();
logger.error("Unable to close " + clazz, exception);
}
}
private Serializable copy(Serializable oldValue)
throws ObjectCannotBeCopiedException {
Serializable newValue = null;
ByteArrayInputStream oldValueInputStream = null;
ByteArrayOutputStream oldValueOutputStream = new ByteArrayOutputStream();
ObjectInputStream newValueInputStream = null;
ObjectOutputStream newValueOutputStream = null;
try {
newValueOutputStream = new ObjectOutputStream(oldValueOutputStream);
newValueOutputStream.writeObject(oldValue);
byte[] oldValueAsByteArray = oldValueOutputStream.toByteArray();
oldValueInputStream = new ByteArrayInputStream(oldValueAsByteArray);
newValueInputStream = new ObjectInputStream(oldValueInputStream);
newValue = (Serializable) newValueInputStream.readObject();
} catch (Exception exception) {
String errMsg = "Unable to copy value " + oldValue;
throw new ObjectCannotBeCopiedException(errMsg, exception);
} finally {
close(newValueInputStream);
close(newValueOutputStream);
close(oldValueInputStream);
close(oldValueOutputStream);
}
return newValue;
}
}