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

org.apache.ojb.broker.cache.ObjectCacheDefaultImpl Maven / Gradle / Ivy

Go to download

ObJectRelationalBridge (OJB) is an Object/Relational mapping tool that allows transparent persistence for Java Objects against relational databases.

The newest version!
package org.apache.ojb.broker.cache;

/* Copyright 2004-2005 The Apache Software Foundation
 *
 * 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.
 */

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.PBStateEvent;
import org.apache.ojb.broker.PBStateListener;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;

/**
 * This global ObjectCache stores all Objects loaded by the PersistenceBroker
 * from a DB using a static {@link java.util.Map}. This means each {@link ObjectCache}
 * instance associated with all {@link PersistenceBroker} instances use the same
 * Map to cache objects. This could lead in "dirty-reads" (similar to read-uncommitted
 * mode in DB) when a concurrent thread look up same object modified by another thread.
 * 
* When the PersistenceBroker tries to get an Object by its {@link Identity}. * It first lookups the cache if the object has been already loaded and cached. *

* NOTE: By default objects cached via {@link SoftReference} which allows * objects (softly) referenced by the cache to be reclaimed by the Java Garbage Collector when * they are not longer referenced elsewhere, so lifetime of cached object is limited by *
- the lifetime of the cache object - see property timeout. *
- the garabage collector used memory settings - see property useSoftReferences. *
- the maximum capacity of the cache - see property maxEntry. *

*

* Implementation configuration properties: *

*

*

*

* * * * *

*

* * * *

*

* * * *

*

* * * *

*

* * * *
Property KeyProperty Values
timeout * Lifetime of the cached objects in seconds. * If expired the cached object was not returned * on lookup call (and removed from cache). Default timeout * value is 900 seconds. When set to -1 the lifetime of * the cached object depends only on GC and do never get timed out. *
autoSync * If set true all cached/looked up objects within a PB-transaction are traced. * If the the PB-transaction was aborted all traced objects will be removed from * cache. Default is false. *

* NOTE: This does not prevent "dirty-reads" (more info see above). *

*

* It's not a smart solution for keeping cache in sync with DB but should do the job * in most cases. *
* E.g. if you lookup 1000 objects within a transaction and modify one object and then abort the * transaction, 1000 objects will be passed to cache, 1000 objects will be traced and * all 1000 objects will be removed from cache. If you read these objects without tx or * in a former tx and then modify one object in a tx and abort the tx, only one object was * traced/removed. *

*
cachingKeyType * Determines how the key was build for the cached objects: *
* 0 - Identity object was used as key, this was the default setting. *
* 1 - Idenity + jcdAlias name was used as key. Useful when the same object metadata model * (DescriptorRepository instance) are used for different databases (JdbcConnectionDescriptor) *
* 2 - Identity + model (DescriptorRepository) was used as key. Useful when different metadata * model (DescriptorRepository instance) are used for the same database. Keep in mind that there * was no synchronization between cached objects with same Identity but different metadata model. *
* 3 - all together (1+2) *
useSoftReferences * If set true this class use {@link java.lang.ref.SoftReference} to cache * objects. Default value is true. *
*

* * @author Thomas Mahler * @version $Id: ObjectCacheDefaultImpl.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $ */ public class ObjectCacheDefaultImpl implements ObjectCacheInternal, PBStateListener { private Logger log = LoggerFactory.getLogger(ObjectCacheDefaultImpl.class); public static final String TIMEOUT_PROP = "timeout"; public static final String AUTOSYNC_PROP = "autoSync"; public static final String CACHING_KEY_TYPE_PROP = "cachingKeyType"; public static final String SOFT_REFERENCES_PROP = "useSoftReferences"; /** * static Map held all cached objects */ protected static final Map objectTable = new Hashtable(); private static final ReferenceQueue queue = new ReferenceQueue(); private static long hitCount = 0; private static long failCount = 0; private static long gcCount = 0; protected PersistenceBroker broker; private List identitiesInWork; /** * Timeout of the cached objects. Default was 900 seconds. */ private long timeout = 1000 * 60 * 15; private boolean useAutoSync = false; /** * Determines how the key was build for the cached objects: *
* 0 - Identity object was used as key * 1 - Idenity + jcdAlias name was used as key * 2 - Identity + model (DescriptorRepository) was used as key * 3 - all together (1+2) */ private int cachingKeyType; private boolean useSoftReferences = true; public ObjectCacheDefaultImpl(PersistenceBroker broker, Properties prop) { this.broker = broker; timeout = prop == null ? timeout : (Long.parseLong(prop.getProperty(TIMEOUT_PROP, "" + (60 * 15))) * 1000); useSoftReferences = prop != null && (Boolean.valueOf((prop.getProperty(SOFT_REFERENCES_PROP, "true")).trim())).booleanValue(); cachingKeyType = prop == null ? 0 : (Integer.parseInt(prop.getProperty(CACHING_KEY_TYPE_PROP, "0"))); useAutoSync = prop != null && (Boolean.valueOf((prop.getProperty(AUTOSYNC_PROP, "false")).trim())).booleanValue(); if(useAutoSync) { if(broker != null) { // we add this instance as a permanent PBStateListener broker.addListener(this, true); } else { log.info("Can't enable property '" + AUTOSYNC_PROP + "', because given PB instance is null"); } } identitiesInWork = new ArrayList(); if(log.isEnabledFor(Logger.INFO)) { ToStringBuilder buf = new ToStringBuilder(this); buf.append("timeout", timeout) .append("useSoftReferences", useSoftReferences) .append("cachingKeyType", cachingKeyType) .append("useAutoSync", useAutoSync); log.info("Setup cache: " + buf.toString()); } } /** * Clear ObjectCache. I.e. remove all entries for classes and objects. */ public void clear() { //processQueue(); objectTable.clear(); identitiesInWork.clear(); } public void doInternalCache(Identity oid, Object obj, int type) { //processQueue(); if((obj != null)) { traceIdentity(oid); synchronized(objectTable) { if(log.isDebugEnabled()) log.debug("Cache object " + oid); objectTable.put(buildKey(oid), buildEntry(obj, oid)); } } } /** * Makes object persistent to the Objectcache. * I'm using soft-references to allow gc reclaim unused objects * even if they are still cached. */ public void cache(Identity oid, Object obj) { doInternalCache(oid, obj, ObjectCacheInternal.TYPE_UNKNOWN); } public boolean cacheIfNew(Identity oid, Object obj) { //processQueue(); boolean result = false; Object key = buildKey(oid); if((obj != null)) { synchronized(objectTable) { if(!objectTable.containsKey(key)) { objectTable.put(key, buildEntry(obj, oid)); result = true; } } if(result) traceIdentity(oid); } return result; } /** * Lookup object with Identity oid in objectTable. * Returns null if no matching id is found */ public Object lookup(Identity oid) { processQueue(); hitCount++; Object result = null; CacheEntry entry = (CacheEntry) objectTable.get(buildKey(oid)); if(entry != null) { result = entry.get(); if(result == null || entry.getLifetime() < System.currentTimeMillis()) { /* cached object was removed by gc or lifetime was exhausted remove CacheEntry from map */ gcCount++; remove(oid); // make sure that we return null result = null; } else { /* TODO: Not sure if this makes sense, could help to avoid corrupted objects when changed in tx but not stored. */ traceIdentity(oid); if(log.isDebugEnabled()) log.debug("Object match " + oid); } } else { failCount++; } return result; } /** * Removes an Object from the cache. */ public void remove(Identity oid) { //processQueue(); if(oid != null) { removeTracedIdentity(oid); objectTable.remove(buildKey(oid)); if(log.isDebugEnabled()) log.debug("Remove object " + oid); } } public String toString() { ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE); buf.append("Count of cached objects", objectTable.keySet().size()); buf.append("Lookup hits", hitCount); buf.append("Failures", failCount); buf.append("Reclaimed", gcCount); return buf.toString(); } private void traceIdentity(Identity oid) { if(useAutoSync && (broker != null) && broker.isInTransaction()) { identitiesInWork.add(oid); } } private void removeTracedIdentity(Identity oid) { identitiesInWork.remove(oid); } private void synchronizeWithTracedObjects() { Identity oid; log.info("tx was aborted," + " remove " + identitiesInWork.size() + " traced (potentially modified) objects from cache"); for(Iterator iterator = identitiesInWork.iterator(); iterator.hasNext();) { oid = (Identity) iterator.next(); objectTable.remove(buildKey(oid)); } } public void beforeRollback(PBStateEvent event) { synchronizeWithTracedObjects(); identitiesInWork.clear(); } public void beforeCommit(PBStateEvent event) { // identitiesInWork.clear(); } public void beforeClose(PBStateEvent event) { /* arminw: In managed environments listener method "beforeClose" is called twice (when the PB handle is closed and when the real PB instance is closed/returned to pool). We are only interested in the real close call when all work is done. */ if(!broker.isInTransaction()) { identitiesInWork.clear(); } } public void afterRollback(PBStateEvent event) { } public void afterCommit(PBStateEvent event) { identitiesInWork.clear(); } public void afterBegin(PBStateEvent event) { } public void beforeBegin(PBStateEvent event) { } public void afterOpen(PBStateEvent event) { } private CacheEntry buildEntry(Object obj, Identity oid) { if(useSoftReferences) { return new CacheEntrySoft(obj, oid, queue, timeout); } else { return new CacheEntryHard(obj, oid, timeout); } } private void processQueue() { CacheEntry sv; while((sv = (CacheEntry) queue.poll()) != null) { removeTracedIdentity(sv.getOid()); objectTable.remove(buildKey(sv.getOid())); } } private Object buildKey(Identity oid) { Object key; switch(cachingKeyType) { case 0: key = oid; break; case 1: key = new OrderedTuple(oid, broker.getPBKey().getAlias()); break; case 2: /* this ObjectCache implementation only works in single JVM, so the hashCode of the DescriptorRepository class is unique TODO: problem when different versions of same DR are used */ key = new OrderedTuple(oid, new Integer(broker.getDescriptorRepository().hashCode())); break; case 3: key = new OrderedTuple(oid, broker.getPBKey().getAlias(), new Integer(broker.getDescriptorRepository().hashCode())); break; default: throw new OJBRuntimeException("Unexpected error, 'cacheType =" + cachingKeyType + "' was not supported"); } return key; } //----------------------------------------------------------- // inner class to build unique key for cached objects //----------------------------------------------------------- /** * Implements equals() and hashCode() for an ordered tuple of constant(!) * objects * * @author Gerhard Grosse * @since Oct 12, 2004 */ static final class OrderedTuple { private static int[] multipliers = new int[]{13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 51}; private Object[] elements; private int hashCode; public OrderedTuple(Object element) { elements = new Object[1]; elements[0] = element; hashCode = calcHashCode(); } public OrderedTuple(Object element1, Object element2) { elements = new Object[2]; elements[0] = element1; elements[1] = element2; hashCode = calcHashCode(); } public OrderedTuple(Object element1, Object element2, Object element3) { elements = new Object[3]; elements[0] = element1; elements[1] = element2; elements[2] = element3; hashCode = calcHashCode(); } public OrderedTuple(Object[] elements) { this.elements = elements; this.hashCode = calcHashCode(); } private int calcHashCode() { int code = 7; for(int i = 0; i < elements.length; i++) { int m = i % multipliers.length; code += elements[i].hashCode() * multipliers[m]; } return code; } public boolean equals(Object obj) { if(!(obj instanceof OrderedTuple)) { return false; } else { OrderedTuple other = (OrderedTuple) obj; if(this.hashCode != other.hashCode) { return false; } else if(this.elements.length != other.elements.length) { return false; } else { for(int i = 0; i < elements.length; i++) { if(!this.elements[i].equals(other.elements[i])) { return false; } } return true; } } } public int hashCode() { return hashCode; } public String toString() { StringBuffer s = new StringBuffer(); s.append('{'); for(int i = 0; i < elements.length; i++) { s.append(elements[i]).append('#').append(elements[i].hashCode()).append(','); } s.setCharAt(s.length() - 1, '}'); s.append("#").append(hashCode); return s.toString(); } } //----------------------------------------------------------- // inner classes to wrap cached objects //----------------------------------------------------------- interface CacheEntry { Object get(); Identity getOid(); long getLifetime(); } final static class CacheEntrySoft extends SoftReference implements CacheEntry { private final long lifetime; private final Identity oid; CacheEntrySoft(Object object, final Identity k, final ReferenceQueue q, long timeout) { super(object, q); oid = k; // if timeout is negative, lifetime of object never expire if(timeout < 0) { lifetime = Long.MAX_VALUE; } else { lifetime = System.currentTimeMillis() + timeout; } } public Identity getOid() { return oid; } public long getLifetime() { return lifetime; } } final static class CacheEntryHard implements CacheEntry { private final long lifetime; private final Identity oid; private Object obj; CacheEntryHard(Object object, final Identity k, long timeout) { obj = object; oid = k; // if timeout is negative, lifetime of object never expire if(timeout < 0) { lifetime = Long.MAX_VALUE; } else { lifetime = System.currentTimeMillis() + timeout; } } public Object get() { return obj; } public Identity getOid() { return oid; } public long getLifetime() { return lifetime; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy