/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jdo.spi.persistence.support.sqlstore.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import com.sun.appserv.util.cache.Cache;
import com.sun.appserv.util.cache.CacheListener;
import com.sun.appserv.util.cache.LruCache;
import com.sun.jdo.spi.persistence.support.sqlstore.LogHelperPersistenceManager;
import com.sun.jdo.spi.persistence.support.sqlstore.StateManager;
import com.sun.jdo.spi.persistence.support.sqlstore.VersionConsistencyCache;
import com.sun.jdo.spi.persistence.utility.BucketizedHashtable;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import org.glassfish.persistence.common.I18NHelper;
/**
* A 2-level cache of StateManager instances (i.e., a map of maps). The inner
* map is a BucketizedHashtable or a LRU cache, depending on parameter given at
* construction.
*
* @author Dave Bristor
*/
public class VersionConsistencyCacheImpl implements VersionConsistencyCache {
/** The outermost map of the two-level cache. */
private final Map pcTypeMap = new HashMap();
/** Used to create different kinds of caches. */
// Not final, so that we can create different kinds of caches for testing.
private static CacheFactory cacheFactory;
private final static ResourceBundle messages =
I18NHelper.loadBundle(VersionConsistencyCacheImpl.class);
/** Use the PersistenceManager's logger. */
private static Logger logger = LogHelperPersistenceManager.getLogger();
/** Name of implementation class of LRU cache. */
private static final String LRU_CACHE_CLASSNAME =
"com.sun.appserv.util.cache.LruCache"; // NOI18aN
//
// Cache configuration controls
//
/** Prefix of each property name of configuration item. */
private static final String PROPERTY_PREFIX =
"com.sun.jdo.spi.persistence.support.sqlstore.impl.VersionConsistency."; // NOI18N
/** Determines whether to use LruCache or the default. */
private static boolean lruCache = false;
/** Name of property to choose LRU or basic cache. */
private static final String LRU_CACHE_PROPERTY = PROPERTY_PREFIX + "LruCache"; // NOI18N
/** For both LruCache and BucketizedHashtable. */
private static float loadFactor = 0.75F;
/** Name of property for specifying loadFactor. */
private static final String LOAD_FACTOR_PROPERTY =
PROPERTY_PREFIX + "loadFactor"; // NOI18N
/** For BucketizedHashtable only. */
private static int bucketSize = 13;
/** Name of property for specifying bucketSize. */
private static final String BUCKET_SIZE_PROPERTY =
PROPERTY_PREFIX + "bucketSize"; // NOI18N
/** For BucketizedHashtable only. */
private static int initialCapacity = 131;
/** Name of property for specifying initialCapacity. */
private static final String INITIAL_CAPACITY_PROPERTY =
PROPERTY_PREFIX + "initialCapacity"; // NOI18N
/** For LruCache only. */
private static int maxEntries = 131;
/** Name of property for specifying maxEntries. */
private static final String MAX_ENTRIES_PROPERTY =
PROPERTY_PREFIX + "maxEntries"; // NOI18N
/** LruCache only, 10 minute timeout */
private static long timeout = 1000L * 60 * 10;
/** Name of property for specifying timeout. */
private static final String TIMEOUT_PROPERTY =
PROPERTY_PREFIX + "timeout"; // NOI18N
// Create the cache factory
static {
cacheFactory = createCacheFactory();
}
/** Empty default constructor. */
private VersionConsistencyCacheImpl() {
}
/** Creates a cache with desired performance. This constructor is
* expected to be used for unit testing ONLY.
* @param highPerf If true, use LruCache, else use BucketizedHashtable.
*/
protected VersionConsistencyCacheImpl(boolean highPerf) {
if (highPerf) {
cacheFactory = new LruCacheFactory();
} else {
cacheFactory = new BasicCacheFactory();
}
}
/**
* Create a cache. The performance characteristics of the cache depends
* on the setting of the runtime properties. If the flag
* <code>com.sun.jdo.spi.persistence.support.sqlstore.impl.VersionConsistency.LruCache</code>
* is true, then the LruCache cache is used. If it has some other value,
* the BucketizedHashtable cache is used. If not set, but we can load
* the LruCache class, the LruCache cache is used. Otherwise, we use
* the BucketizedHashtable cache. Other properties control particulars
* of those two caches.
*/
static VersionConsistencyCache create() {
return new VersionConsistencyCacheImpl();
}
/**
* Create a CacheFactory. Uses system properties to determine what kind of
* cache will be returned by the factory.
*/
static CacheFactory createCacheFactory() {
CacheFactory rc = null;
loadFactor = getFloatValue(LOAD_FACTOR_PROPERTY, loadFactor);
bucketSize = getIntValue(BUCKET_SIZE_PROPERTY, bucketSize);
initialCapacity = getIntValue(INITIAL_CAPACITY_PROPERTY, initialCapacity);
maxEntries = getIntValue(MAX_ENTRIES_PROPERTY, maxEntries);
timeout = getLongValue(TIMEOUT_PROPERTY, timeout);
// Determine whether to use LRU cache or not.
boolean lruCache = false;
try {
// Don't use Boolean.getBoolean, because we want to know if the
// flag is given or not.
String s = System.getProperty(LRU_CACHE_PROPERTY);
if (s != null) {
lruCache = Boolean.valueOf(s).booleanValue();
if (lruCache) {
// If user specifies lruCache, but it is not available,
// log a WARNING and use the basic cache.
try {
Class.forName(LRU_CACHE_CLASSNAME);
} catch (Exception ex) {
logger.warning(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.lrucachenotfound")); // NOI18N
lruCache = false;
}
}
} else {
// No flag given: Try to load LRU cache
try {
Class.forName(LRU_CACHE_CLASSNAME);
lruCache = true;
} catch (Exception ex) {
// LRU cache not found, so use default
}
}
} catch (Exception ex) {
// This probably should not happen, but fallback to the
// default cache just in case.
lruCache = false;
logger.warning(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.unexpectedduringcreate", ex));// NOI18N
}
if (lruCache) {
rc = new LruCacheFactory();
} else {
rc = new BasicCacheFactory();
}
if (logger.isLoggable(Logger.FINER)) {
String values =
"\nloadFactor= " + loadFactor // NOI18N
+ "\nbucketSize= " + bucketSize // NOI18N
+ "\ninitialCapacity=" + initialCapacity // NOI18N
+ "\nmaxEntries=" + maxEntries // NOI18N
+ "\ntimeout=" + timeout // NOI18N
+ "\nlruCache=" + lruCache; // NOI18N
logger.finer(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.created",
values)); // NOI18N
}
return rc;
}
/**
* Returns the value for the given property name. If not available,
* returns the default value. If the property's value cannot be parsed as
* an integer, logs a warning.
* @param propName Name of property for value
* @param defaultVal Default value used if property is not set.
* @return value for the property.
*/
private static int getIntValue(String propName, int defaultVal) {
int rc = defaultVal;
String valString = System.getProperty(propName);
if (null != valString && valString.length() > 0) {
try {
rc = Integer.parseInt(valString);
} catch (NumberFormatException ex) {
logBadConfigValue(propName, valString);
}
}
return rc;
}
/**
* Returns the value for the given property name. If not available,
* returns the default value. If the property's value cannot be parsed as
* a float, logs a warning.
* @param propName Name of property for value
* @param defaultVal Default value used if property is not set.
* @return value for the property.
*/
private static float getFloatValue(String propName, float defaultVal) {
float rc = defaultVal;
String valString = System.getProperty(propName);
if (null != valString && valString.length() > 0) {
try {
rc = Float.parseFloat(valString);
} catch (NumberFormatException ex) {
logBadConfigValue(propName, valString);
}
}
return rc;
}
/**
* Returns the value for the given property name. If not available,
* returns the default value. If the property's value cannot be parsed as
* a long, logs a warning.
* @param propName Name of property for value
* @param defaultVal Default value used if property is not set.
* @return value for the property.
*/
private static long getLongValue(String propName, long defaultVal) {
long rc = defaultVal;
String valString = System.getProperty(propName);
if (null != valString && valString.length() > 0) {
try {
rc = Long.parseLong(valString);
} catch (NumberFormatException ex) {
logBadConfigValue(propName, valString);
}
}
return rc;
}
/**
* Logs a warning that the property's value is invalid.
* @param propName Name of property
* @param valString Value of property as a String.
*/
private static void logBadConfigValue(String propName, String valString) {
logger.warning(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.badconfigvalue", // NOI18N
propName, valString));
}
/**
* @see VersionConsistencyCache#put
*/
public StateManager put(Class pcType, Object oid, StateManager sm) {
boolean logAtFinest = logger.isLoggable(Logger.FINEST);
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.put.entering", // NOI18N
new Object[] {pcType, oid, sm}));
}
StateManager rc = null;
VCCache oid2sm = null;
synchronized (pcTypeMap) {
oid2sm = (VCCache) pcTypeMap.get(pcType);
if (null == oid2sm) {
oid2sm = cacheFactory.create();
pcTypeMap.put(pcType, oid2sm);
}
}
rc = oid2sm.put(oid, sm);
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.put.returning", // NOI18N
rc));
}
return rc;
}
/**
* @see VersionConsistencyCache#get
*/
public StateManager get(Class pcType, Object oid) {
boolean logAtFinest = logger.isLoggable(Logger.FINEST);
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.get.entering", // NOI18N
new Object[] {pcType, oid}));
}
StateManager rc = null;
VCCache oid2sm = null;
synchronized (pcTypeMap) {
oid2sm = (VCCache) pcTypeMap.get(pcType);
}
if (null != oid2sm) {
rc = oid2sm.get(oid);
}
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.get.returning", // NOI18N
rc));
}
return rc;
}
/**
* @see VersionConsistencyCache#remove
*/
public StateManager remove(Class pcType, Object oid) {
boolean logAtFinest = logger.isLoggable(Logger.FINEST);
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.remove.entering", // NOI18N
new Object[] {pcType, oid}));
}
StateManager rc = null;
synchronized (pcTypeMap) {
VCCache oid2sm = (VCCache) pcTypeMap.get(pcType);
if (null != oid2sm) {
rc = (StateManager) oid2sm.remove(oid);
if (oid2sm.isEmpty()) {
pcTypeMap.remove(pcType);
}
}
}
if (logAtFinest) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.remove.returning", // NOI18N
rc));
}
return rc;
}
/**
* This implementation does nothing. Instead, we create buckets for each
* pcType as-needed; see {@link #put}
*/
public void addPCType(Class pcType) {
if (logger.isLoggable(Logger.FINEST)) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.addpctype", // NOI18N
pcType));
}
// Intentionally empty
}
/**
* @see VersionConsistencyCache#removePCType
*/
public void removePCType(Class pcType) {
if (logger.isLoggable(Logger.FINEST)) {
logger.finest(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.removepctype", // NOI18N
pcType));
}
synchronized (pcTypeMap) {
VCCache oid2sm = (VCCache) pcTypeMap.get(pcType);
if (null != oid2sm) {
oid2sm.clear();
}
pcTypeMap.remove(pcType);
}
}
/**
* @return the number of elements in the cache.
*/
public int size() {
int rc = 0;
synchronized (pcTypeMap) {
for (Iterator i = pcTypeMap.keySet().iterator(); i.hasNext();) {
VCCache oid2sm = (VCCache) pcTypeMap.get(i.next());
rc += oid2sm.size();
}
}
return rc;
}
/**
* @return true if this cache is based on LRU cache; false otherwise.
*/
public boolean isHighPerf() {
return LruCacheFactory.class.equals(cacheFactory.getClass());
}
//
// Support for the inner map. It is either HashMap- or Cache- based.
//
/** Provides cache operations of put, get, and remove. */
interface VCCache {
/** @see Map#put */
public StateManager put(Object key, StateManager value);
/** @see Map#get */
public StateManager get(Object key);
/** @see Map#remove */
public StateManager remove(Object key);
/** @see Map#clear */
public void clear();
/** @see Map#isEmpty */
public boolean isEmpty();
/** @see Map#size */
public int size();
}
/**
* VCCache that is HashMap-based. The methods are not synchronized but
* the underlying implemention <em>is</em>synchronized.
*/
static class BasicVCCache implements VCCache {
private final Map cache;
BasicVCCache() {
if (logger.isLoggable(Logger.FINER)) {
logger.finer(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.usinghashmap", // NOI18N
new Object[] {
new Integer(bucketSize),
new Long(initialCapacity),
new Float(loadFactor)}));
}
cache = Collections.synchronizedMap(
new BucketizedHashtable(
bucketSize, initialCapacity, loadFactor));
}
/** @see Map#put */
public StateManager put(Object key, StateManager value) {
return (StateManager) cache.put(key, value);
}
/** @see Map#get */
public StateManager get(Object key) {
return (StateManager) cache.get(key);
}
/** @see Map#remove */
public StateManager remove(Object key) {
return (StateManager) cache.remove(key);
}
/** @see Map#clear */
public void clear() {
cache.clear();
}
/** @see Map#isEmpty */
public boolean isEmpty() {
return cache.isEmpty();
}
/** @see Map#size */
public int size() {
return cache.size();
}
}
/**
* VCCache that uses LRU cachd. Methods are not synchronized, but
* underlying cache implementation <em>is</em>.
*/
static class LruVCCache implements VCCache {
/**
* We can't use the interface type Cache because we need to be able to
* clear out the cache, which is only supported by the implementation.
*/
private final Cache cache;
/**
* @param maxEntries maximum number of entries expected in the cache
* @param loadFactor the load factor
* @param timeout to be used to trim the expired entries
*/
LruVCCache(int maxEntries, long timeout, float loadFactor) {
if (logger.isLoggable(Logger.FINER)) {
logger.finer(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.usinglrucache", // NOI18N
new Object[] {
new Integer(maxEntries),
new Long(timeout),
new Float(loadFactor)}));
}
LruCache c = new LruCache();
c.init(maxEntries, timeout, loadFactor, (Properties) null);
c.addCacheListener(
new CacheListener() {
public void trimEvent(Object key, Object value) {
cache.remove(key);
if (logger.isLoggable(Logger.FINER)) {
logger.finer(
I18NHelper.getMessage(
messages,
"jdo.versionconsistencycacheimpl.trimevent")); // NOI18N
}
}
});
cache = c;
}
/** @see Map#put */
public StateManager put(Object key, StateManager value) {
return (StateManager) cache.put(key, value);
}
/** @see Map#get */
public StateManager get(Object key) {
return (StateManager) cache.get(key);
}
/** @see Map#remove */
public StateManager remove(Object key) {
return (StateManager) cache.remove(key);
}
/** @see Map#clear */
public void clear() {
cache.clear();
}
/** @see Map#isEmpty */
public boolean isEmpty() {
return cache.isEmpty();
}
/** @see Map#size */
public int size() {
return cache.getEntryCount();
}
}
//
// Factory for creating VCCache instances.
//
/** Provides for creating an instance of a VCCache. */
interface CacheFactory {
/** @return an instance of a VCCache. */
public VCCache create();
}
/** Provides for creating an instance of a BasicVCCache. */
static class BasicCacheFactory implements CacheFactory {
/** @return an instance of a BasicVCCache. */
public VCCache create() {
return new BasicVCCache();
}
}
/** Provides for creating an instance of a LruVCCache. */
static class LruCacheFactory implements CacheFactory {
/** @return an instance of a LruVCCache. */
public VCCache create() {
return new LruVCCache(maxEntries, timeout, loadFactor);
}
}
}