com.ibatis.sqlmap.engine.cache.CacheModel Maven / Gradle / Ivy
/*
* Copyright 2004 Clinton Begin
*
* 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 com.ibatis.sqlmap.engine.cache;
import com.ibatis.common.logging.Log;
import com.ibatis.common.logging.LogFactory;
import com.ibatis.sqlmap.engine.mapping.statement.ExecuteListener;
import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
import java.io.*;
import java.util.*;
/**
* Wrapper for Caches.
*/
public class CacheModel implements ExecuteListener {
private static final Log log = LogFactory.getLog(CacheModel.class);
private static final int MAX_OBJECT_LOG_SIZE = 200;
/**
* This is used to represent null objects that are returned from the cache so
* that they can be cached, too.
*/
public static final Object NULL_OBJECT = new String("SERIALIZABLE_NULL_OBJECT");
private int requests = 0;
private int hits = 0;
/**
* Constant to turn off periodic cache flushes
*/
private static final long NO_FLUSH_INTERVAL = -99999;
private String id;
private boolean readOnly;
private boolean serialize;
private long lastFlush;
private long flushInterval;
private long flushIntervalSeconds;
private Set flushTriggerStatements;
private CacheController controller;
private String resource;
/**
* Default constructor
*/
public CacheModel() {
this.flushInterval = NO_FLUSH_INTERVAL;
this.flushIntervalSeconds = NO_FLUSH_INTERVAL;
this.lastFlush = System.currentTimeMillis();
this.flushTriggerStatements = new HashSet();
}
/**
* Getter for the cache model's id
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Setter for the cache model's id
*
* @param id - the new id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter for read-only property
*
* @return true if a read-only model
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Setter for read-only property
*
* @param readOnly - the new setting
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Getter to tell if the cache serializes
*
* @return true if the cache model serializes objects
*/
public boolean isSerialize() {
return serialize;
}
/**
* Setter to tell the cache to serialize objects
*
* @param serialize - if the cache model is to serialize objects
*/
public void setSerialize(boolean serialize) {
this.serialize = serialize;
}
/**
* Getter for resource property
*
* @return the value of the resource property
*/
public String getResource() {
return resource;
}
/**
* Setter for resource property
*
* @param resource - the new value
*/
public void setResource(String resource) {
this.resource = resource;
}
/**
* Sets up the controller for the cache model
*
* @throws ClassNotFoundException - if the class cannot be found
* @throws InstantiationException - if the class cannot be instantiated
* @throws IllegalAccessException - if the classes constructor is not accessible
*/
public void setCacheController(CacheController controller)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
this.controller = controller;
}
/**
* Getter for flushInterval property
*
* @return The flushInterval (in milliseconds)
*/
public long getFlushInterval() {
return flushInterval;
}
/**
* Getter for flushInterval property
*
* @return The flushInterval (in milliseconds)
*/
public long getFlushIntervalSeconds() {
return flushIntervalSeconds;
}
/**
* Setter for flushInterval property
*
* @param flushInterval The new flushInterval (in milliseconds)
*/
public void setFlushInterval(long flushInterval) {
this.flushInterval = flushInterval;
this.flushIntervalSeconds = flushInterval / 1000;
}
/**
* Adds a flushTriggerStatment. When a flushTriggerStatment is executed, the
* cache is flushed (cleared).
*
* @param statementName The statement to add.
*/
public void addFlushTriggerStatement(String statementName) {
flushTriggerStatements.add(statementName);
}
/**
* Gets an Iterator containing all flushTriggerStatment objects for this cache.
*
* @return The Iterator
*/
public Iterator getFlushTriggerStatementNames() {
return flushTriggerStatements.iterator();
}
/**
* ExecuteListener event. This will be called by a MappedStatement
* for which this cache is registered as a ExecuteListener. It will
* be called each time an executeXXXXXX method is called. In the
* case of the Cache class, it is registered in order to flush the
* cache whenever a certain statement is executed.
* (i.e. the flushOnExecute cache policy)
*
* @param statement The statement to execute
*/
public void onExecuteStatement(MappedStatement statement) {
flush();
}
/**
* Returns statistical information about the cache.
*
* @return the number of cache hits divided by the total requests
*/
public double getHitRatio() {
return (double) hits / (double) requests;
}
/**
* Configures the cache
*
* @param props
*/
public void configure(Properties props) {
controller.setProperties(props);
}
/**
* Clears the cache
*/
public void flush() {
synchronized (this) {
controller.flush(this);
lastFlush = System.currentTimeMillis();
if ( log.isDebugEnabled() ) {
log("flushed", false, null);
}
}
}
/**
* Get an object out of the cache.
* A side effect of this method is that is may clear the cache if it has not been
* cleared in the flushInterval.
*
* @param key The key of the object to be returned
* @return The cached object (or null)
*/
public Object getObject(CacheKey key) {
Object value = null;
synchronized (this) {
if (flushInterval != NO_FLUSH_INTERVAL
&& System.currentTimeMillis() - lastFlush > flushInterval) {
flush();
}
value = controller.getObject(this, key);
if (serialize && !readOnly &&
(value != NULL_OBJECT && value != null)) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) value);
ObjectInputStream ois = new ObjectInputStream(bis);
value = ois.readObject();
ois.close();
} catch (Exception e) {
throw new RuntimeException("Error caching serializable object. Be sure you're not attempting to use " +
"a serialized cache for an object that may be taking advantage of lazy loading. Cause: " + e, e);
}
}
requests++;
if (value != null) {
hits++;
}
if ( log.isDebugEnabled() ) {
if ( value != null ) {
log("retrieved object", true, value);
}
else {
log("cache miss", false, null);
}
}
}
return value;
}
/**
* Add an object to the cache
*
* @param key The key of the object to be cached
* @param value The object to be cached
*/
public void putObject(CacheKey key, Object value) {
if (null == value) value = NULL_OBJECT;
synchronized ( this ) {
if (serialize && !readOnly && value != NULL_OBJECT) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
oos.close();
value = bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Error caching serializable object. Cause: " + e, e);
}
}
controller.putObject(this, key, value);
if ( log.isDebugEnabled() ) {
log("stored object", true, value);
}
}
}
/**
* Get the maximum size of an object in the log output.
*
* @return Maximum size of a logged object in the output
*/
protected int getMaxObjectLogSize() {
return MAX_OBJECT_LOG_SIZE;
}
/**
* Log a cache action. Since this method is pretty heavy
* weight, it's best to enclose it with a log.isDebugEnabled()
* when called.
*
* @param action String to output
* @param addValue Add the value being cached to the log
* @param cacheValue The value being logged
*/
protected void log(String action, boolean addValue,
Object cacheValue) {
StringBuffer output = new StringBuffer("Cache '");
output.append(getId());
output.append("': ");
output.append(action);
if ( addValue ) {
String cacheObjectStr = (cacheValue == null ? "null" : cacheValue.toString());
output.append(" '");
if ( cacheObjectStr.length() < getMaxObjectLogSize() ) {
output.append(cacheObjectStr.toString());
}
else {
output.append(cacheObjectStr.substring(1,
getMaxObjectLogSize()));
output.append("...");
}
output.append("'");
}
log.debug(output.toString());
}
public void setControllerProperties(Properties cacheProps) {
controller.setProperties(cacheProps);
}
}