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

com.ibatis.sqlmap.engine.cache.CacheModel Maven / Gradle / Ivy

/*
 * Copyright 2004-2023 the original author or authors.
 *
 * 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
 *
 *    https://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.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

/**
 * Wrapper for Caches.
 */
public class CacheModel implements ExecuteListener {

  /** The Constant log. */
  private static final Log log = LogFactory.getLog(CacheModel.class);

  /** The Constant MAX_OBJECT_LOG_SIZE. */
  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 = "SERIALIZABLE_NULL_OBJECT";

  /** The requests. */
  private int requests = 0;

  /** The hits. */
  private int hits = 0;

  /** Constant to turn off periodic cache flushes. */
  private static final long NO_FLUSH_INTERVAL = -99999;

  /** The id. */
  private String id;

  /** The read only. */
  private boolean readOnly;

  /** The serialize. */
  private boolean serialize;

  /** The last flush. */
  private long lastFlush;

  /** The flush interval. */
  private long flushInterval;

  /** The flush interval seconds. */
  private long flushIntervalSeconds;

  /** The flush trigger statements. */
  private Set flushTriggerStatements;

  /** The controller. */
  private CacheController controller;

  /** The resource. */
  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.
   *
   * @param controller
   *          the new cache controller
   *
   * @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
   *          the 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) {
    StringBuilder output = new StringBuilder("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);
      } else {
        output.append(cacheObjectStr.substring(1, getMaxObjectLogSize()));
        output.append("...");
      }
      output.append("'");
    }
    log.debug(output.toString());
  }

  /**
   * Sets the controller properties.
   *
   * @param cacheProps
   *          the new controller properties
   */
  public void setControllerProperties(Properties cacheProps) {
    controller.setProperties(cacheProps);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy