001: /*
002: * Copyright 2004 Clinton Begin
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package com.ibatis.sqlmap.engine.cache;
017:
018: import com.ibatis.common.logging.Log;
019: import com.ibatis.common.logging.LogFactory;
020: import com.ibatis.common.resources.Resources;
021: import com.ibatis.sqlmap.engine.mapping.statement.ExecuteListener;
022: import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
023:
024: import java.io.*;
025: import java.util.*;
026:
027: /**
028: * Wrapper for Caches.
029: */
030: public class CacheModel implements ExecuteListener {
031:
032: private static final Log log = LogFactory.getLog(CacheModel.class);
033:
034: private static final int MAX_OBJECT_LOG_SIZE = 200;
035:
036: /**
037: * This is used to represent null objects that are returned from the cache so
038: * that they can be cached, too.
039: */
040: public static final Object NULL_OBJECT = new Object();
041: private int requests = 0;
042: private int hits = 0;
043:
044: /**
045: * Constant to turn off periodic cache flushes
046: */
047: private static final long NO_FLUSH_INTERVAL = -99999;
048:
049: private String id;
050:
051: private boolean readOnly;
052: private boolean serialize;
053:
054: private long lastFlush;
055: private long flushInterval;
056: private long flushIntervalSeconds;
057: private Set flushTriggerStatements;
058:
059: private CacheController controller;
060:
061: private String resource;
062:
063: /**
064: * Default constructor
065: */
066: public CacheModel() {
067: this .flushInterval = NO_FLUSH_INTERVAL;
068: this .flushIntervalSeconds = NO_FLUSH_INTERVAL;
069: this .lastFlush = System.currentTimeMillis();
070: this .flushTriggerStatements = new HashSet();
071: }
072:
073: /**
074: * Getter for the cache model's id
075: *
076: * @return the id
077: */
078: public String getId() {
079: return id;
080: }
081:
082: /**
083: * Setter for the cache model's id
084: *
085: * @param id - the new id
086: */
087: public void setId(String id) {
088: this .id = id;
089: }
090:
091: /**
092: * Getter for read-only property
093: *
094: * @return true if a read-only model
095: */
096: public boolean isReadOnly() {
097: return readOnly;
098: }
099:
100: /**
101: * Setter for read-only property
102: *
103: * @param readOnly - the new setting
104: */
105: public void setReadOnly(boolean readOnly) {
106: this .readOnly = readOnly;
107: }
108:
109: /**
110: * Getter to tell if the cache serializes
111: *
112: * @return true if the cache model serializes objects
113: */
114: public boolean isSerialize() {
115: return serialize;
116: }
117:
118: /**
119: * Setter to tell the cache to serialize objects
120: *
121: * @param serialize - if the cache model is to serialize objects
122: */
123: public void setSerialize(boolean serialize) {
124: this .serialize = serialize;
125: }
126:
127: /**
128: * Getter for resource property
129: *
130: * @return the value of the resource property
131: */
132: public String getResource() {
133: return resource;
134: }
135:
136: /**
137: * Setter for resource property
138: *
139: * @param resource - the new value
140: */
141: public void setResource(String resource) {
142: this .resource = resource;
143: }
144:
145: /**
146: * Sets up the controller for the cache model
147: *
148: * @param implementation - the class (FQCN) for the controller
149: * @throws ClassNotFoundException - if the class cannot be found
150: * @throws InstantiationException - if the class cannot be instantiated
151: * @throws IllegalAccessException - if the classes constructor is not accessible
152: */
153: public void setControllerClassName(String implementation)
154: throws ClassNotFoundException, InstantiationException,
155: IllegalAccessException {
156: Class clazz = Resources.classForName(implementation);
157: controller = (CacheController) Resources.instantiate(clazz);
158: }
159:
160: /**
161: * Getter for flushInterval property
162: *
163: * @return The flushInterval (in milliseconds)
164: */
165: public long getFlushInterval() {
166: return flushInterval;
167: }
168:
169: /**
170: * Getter for flushInterval property
171: *
172: * @return The flushInterval (in milliseconds)
173: */
174: public long getFlushIntervalSeconds() {
175: return flushIntervalSeconds;
176: }
177:
178: /**
179: * Setter for flushInterval property
180: *
181: * @param flushInterval The new flushInterval (in milliseconds)
182: */
183: public void setFlushInterval(long flushInterval) {
184: this .flushInterval = flushInterval;
185: this .flushIntervalSeconds = flushInterval / 1000;
186: }
187:
188: /**
189: * Adds a flushTriggerStatment. When a flushTriggerStatment is executed, the
190: * cache is flushed (cleared).
191: *
192: * @param statementName The statement to add.
193: */
194: public void addFlushTriggerStatement(String statementName) {
195: flushTriggerStatements.add(statementName);
196: }
197:
198: /**
199: * Gets an Iterator containing all flushTriggerStatment objects for this cache.
200: *
201: * @return The Iterator
202: */
203: public Iterator getFlushTriggerStatementNames() {
204: return flushTriggerStatements.iterator();
205: }
206:
207: /**
208: * ExecuteListener event. This will be called by a MappedStatement
209: * for which this cache is registered as a ExecuteListener. It will
210: * be called each time an executeXXXXXX method is called. In the
211: * case of the Cache class, it is registered in order to flush the
212: * cache whenever a certain statement is executed.
213: * (i.e. the flushOnExecute cache policy)
214: *
215: * @param statement The statement to execute
216: */
217: public void onExecuteStatement(MappedStatement statement) {
218: flush();
219: }
220:
221: /**
222: * Returns statistical information about the cache.
223: *
224: * @return the number of cache hits divided by the total requests
225: */
226: public double getHitRatio() {
227: return (double) hits / (double) requests;
228: }
229:
230: /**
231: * Configures the cache
232: *
233: * @param props
234: */
235: public void configure(Properties props) {
236: controller.configure(props);
237: }
238:
239: /**
240: * Clears the cache
241: */
242: public void flush() {
243: synchronized (this ) {
244: controller.flush(this );
245: lastFlush = System.currentTimeMillis();
246: if (log.isDebugEnabled()) {
247: log("flushed", false, null);
248: }
249: }
250: }
251:
252: /**
253: * Get an object out of the cache.
254: * A side effect of this method is that is may clear the cache if it has not been
255: * cleared in the flushInterval.
256: *
257: * @param key The key of the object to be returned
258: * @return The cached object (or null)
259: */
260: public Object getObject(CacheKey key) {
261: Object value = null;
262: synchronized (this ) {
263: if (flushInterval != NO_FLUSH_INTERVAL
264: && System.currentTimeMillis() - lastFlush > flushInterval) {
265: flush();
266: }
267:
268: value = controller.getObject(this , key);
269: if (serialize && !readOnly
270: && (value != NULL_OBJECT && value != null)) {
271: try {
272: ByteArrayInputStream bis = new ByteArrayInputStream(
273: (byte[]) value);
274: ObjectInputStream ois = new ObjectInputStream(bis);
275: value = ois.readObject();
276: ois.close();
277: } catch (Exception e) {
278: throw new RuntimeException(
279: "Error caching serializable object. Be sure you're not attempting to use "
280: + "a serialized cache for an object that may be taking advantage of lazy loading. Cause: "
281: + e, e);
282: }
283: }
284: requests++;
285: if (value != null) {
286: hits++;
287: }
288: if (log.isDebugEnabled()) {
289: if (value != null) {
290: log("retrieved object", true, value);
291: } else {
292: log("cache miss", false, null);
293: }
294: }
295: }
296: return value;
297: }
298:
299: /**
300: * Add an object to the cache
301: *
302: * @param key The key of the object to be cached
303: * @param value The object to be cached
304: */
305: public void putObject(CacheKey key, Object value) {
306: if (null == value)
307: value = NULL_OBJECT;
308: synchronized (this ) {
309: if (serialize && !readOnly && value != NULL_OBJECT) {
310: try {
311: ByteArrayOutputStream bos = new ByteArrayOutputStream();
312: ObjectOutputStream oos = new ObjectOutputStream(bos);
313: oos.writeObject(value);
314: oos.flush();
315: oos.close();
316: value = bos.toByteArray();
317: } catch (IOException e) {
318: throw new RuntimeException(
319: "Error caching serializable object. Cause: "
320: + e, e);
321: }
322: }
323: controller.putObject(this , key, value);
324: if (log.isDebugEnabled()) {
325: log("stored object", true, value);
326: }
327: }
328: }
329:
330: /**
331: * Get the maximum size of an object in the log output.
332: *
333: * @return Maximum size of a logged object in the output
334: */
335: protected int getMaxObjectLogSize() {
336: return MAX_OBJECT_LOG_SIZE;
337: }
338:
339: /**
340: * Log a cache action. Since this method is pretty heavy
341: * weight, it's best to enclose it with a log.isDebugEnabled()
342: * when called.
343: *
344: * @param action String to output
345: * @param addValue Add the value being cached to the log
346: * @param cacheValue The value being logged
347: */
348: protected void log(String action, boolean addValue,
349: Object cacheValue) {
350: StringBuffer output = new StringBuffer("Cache '");
351: output.append(getId());
352: output.append("': ");
353: output.append(action);
354: if (addValue) {
355: String cacheObjectStr = (cacheValue == null ? "null"
356: : cacheValue.toString());
357: output.append(" '");
358: if (cacheObjectStr.length() < getMaxObjectLogSize()) {
359: output.append(cacheObjectStr.toString());
360: } else {
361: output.append(cacheObjectStr.substring(1,
362: getMaxObjectLogSize()));
363: output.append("...");
364: }
365: output.append("'");
366: }
367: log.debug(output.toString());
368: }
369: }
|