package org.apache.torque.manager;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.FastArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.JCS;
import org.apache.jcs.access.GroupCacheAccess;
import org.apache.jcs.access.exception.CacheException;
import org.apache.torque.Torque;
import org.apache.torque.TorqueException;
import org.apache.torque.om.ObjectKey;
import org.apache.torque.om.Persistent;
/**
* This class contains common functionality of a Manager for
* instantiating OM's.
*
* @param <T> the class of the database object managed by this class.
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
* @version $Id: AbstractBaseManager.java 1379317 2012-08-31 06:56:48Z tfischer $
*/
public abstract class AbstractBaseManager<T extends Persistent>
implements Serializable
{
/** serial version */
private static final long serialVersionUID = 509798299473481305L;
/** the log */
protected static final Log log = LogFactory.getLog(AbstractBaseManager.class);
/** used to cache the om objects. cache is set by the region property */
protected transient GroupCacheAccess cache;
/** method results cache */
protected MethodResultCache mrCache;
/** The OM class that the service will instantiate. */
private Class<T> omClass;
/** The name of the OM class that the service will instantiate. */
private String className;
/** The cache region used for JCS. */
private String region;
/** Whether the cache manager has already registered its cache Listeners. */
private boolean isNew = true;
/** The fields which are valid fields of interest for a listener. */
protected Map<String, ?> validFields;
/** The listeners for this manager. */
protected Map<String, FastArrayList> listenersMap
= new HashMap<String, FastArrayList>();
/**
* Get the Class instance
*
* @return the om class
*/
protected Class<T> getOMClass()
{
return omClass;
}
/**
* Set the Class that will be instantiated by this manager
*
* @param omClass the om class
*/
protected void setOMClass(Class<T> omClass)
{
this.omClass = omClass;
}
/**
* Get a fresh instance of an om
*
* @return an instance of the om class
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected T getOMInstance()
throws TorqueException
{
try
{
return omClass.newInstance();
}
catch (InstantiationException e)
{
throw new TorqueException("Could not instantiate " + getClassName(), e);
}
catch (IllegalAccessException e)
{
throw new TorqueException("Could not access " + getClassName(), e);
}
}
/**
* Get the classname to instantiate for getInstance()
*
* @return value of className.
*/
public String getClassName()
{
return className;
}
/**
* Set the classname to instantiate for getInstance()
* @param v Value to assign to className.
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
@SuppressWarnings("unchecked")
public void setClassName(String v)
throws TorqueException
{
this.className = v;
try
{
Class<?> clazz = Class.forName(getClassName());
if (Persistent.class.isAssignableFrom(clazz))
{
setOMClass((Class<T>) clazz);
}
else
{
throw new TorqueException(
getClassName()
+ " does not implement the interface Persistent");
}
}
catch (ClassNotFoundException cnfe)
{
throw new TorqueException("Could not load " + getClassName(), cnfe);
}
}
/**
* Return an instance of an om based on the id
*
* @param id the primary key of the object
* @return the object from persistent storage or from cache
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected T getOMInstance(ObjectKey id)
throws TorqueException
{
return getOMInstance(id, true);
}
/**
* Return an instance of an om based on the id
*
* @param key the primary key of the object
* @param fromCache true if the object should be retrieved from cache
* @return the object from persistent storage or from cache
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected T getOMInstance(ObjectKey key, boolean fromCache)
throws TorqueException
{
T om = null;
if (fromCache)
{
om = cacheGet(key);
}
if (om == null)
{
om = retrieveStoredOM(key);
if (fromCache)
{
putInstanceImpl(om);
}
}
return om;
}
/**
* Get an object from cache
*
* @param key the primary key of the object
* @return the object from cache
*/
@SuppressWarnings("unchecked")
protected T cacheGet(Serializable key)
{
T om = null;
if (cache != null)
{
synchronized (this)
{
om = (T) cache.get(key);
}
}
return om;
}
/**
* Clears the cache
*
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected void clearImpl()
throws TorqueException
{
if (cache != null)
{
try
{
cache.clear();
}
catch (CacheException ce)
{
throw new TorqueException(
"Could not clear cache due to internal JCS error.", ce);
}
}
}
/**
* Disposes of the cache. This triggers a shutdown of the connected cache
* instances. This method should only be used during shutdown of Torque. The
* manager instance will not cache anymore after this call.
*/
public void dispose()
{
if (cache != null)
{
cache.dispose();
cache = null;
}
}
/**
* Remove an object from the cache
*
* @param key the cache key for the object
* @return the object one last time
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
@SuppressWarnings("unchecked")
protected T removeInstanceImpl(Serializable key)
throws TorqueException
{
T oldOm = null;
if (cache != null)
{
try
{
synchronized (this)
{
oldOm = (T) cache.get(key);
cache.remove(key);
}
}
catch (CacheException ce)
{
throw new TorqueException
("Could not remove from cache due to internal JCS error",
ce);
}
}
return oldOm;
}
/**
* Put an object into the cache
*
* @param om the object
* @return if an object with the same key already is in the cache
* this object will be returned, else null
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected T putInstanceImpl(T om)
throws TorqueException
{
ObjectKey key = om.getPrimaryKey();
return putInstanceImpl(key, om);
}
/**
* Put an object into the cache
*
* @param key the cache key for the object
* @param om the object
* @return if an object with this key already is in the cache
* this object will be returned, else null
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
@SuppressWarnings("unchecked")
protected T putInstanceImpl(Serializable key, T om)
throws TorqueException
{
if (getOMClass() != null && !getOMClass().isInstance(om))
{
throw new TorqueException(om + "; class=" + om.getClass().getName()
+ "; id=" + om.getPrimaryKey() + " cannot be cached with "
+ getOMClass().getName() + " objects");
}
T oldOm = null;
if (cache != null)
{
try
{
synchronized (this)
{
oldOm = (T) cache.get(key);
cache.put(key, om);
}
}
catch (CacheException ce)
{
throw new TorqueException
("Could not cache due to internal JCS error", ce);
}
}
return oldOm;
}
/**
* Retrieve an object from persistent storage
*
* @param id the primary key of the object
* @return the object
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected abstract T retrieveStoredOM(ObjectKey id)
throws TorqueException;
/**
* Gets a list of om's based on id's.
*
* @param ids a <code>ObjectKey[]</code> value
* @return a <code>List</code> value
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected List<T> getOMs(ObjectKey[] ids)
throws TorqueException
{
return getOMs(Arrays.asList(ids));
}
/**
* Gets a list of om's based on id's.
*
* @param ids a <code>List</code> of <code>ObjectKey</code>'s
* @return a <code>List</code> value
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected List<T> getOMs(List<? extends ObjectKey> ids)
throws TorqueException
{
return getOMs(ids, true);
}
/**
* Gets a list of om's based on id's.
*
* @param ids a <code>List</code> of <code>ObjectKey</code>'s
* @return a <code>List</code> value
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected List<T> getOMs(List<? extends ObjectKey> ids, boolean fromCache)
throws TorqueException
{
List<T> oms = null;
if (ids != null && ids.size() > 0)
{
// start a new list where we will replace the id's with om's
Map<ObjectKey, T> omsMap = new HashMap<ObjectKey, T>();
List<ObjectKey> newIds = new ArrayList<ObjectKey>(ids.size());
for (ObjectKey key : ids)
{
T om = null;
if (fromCache)
{
om = cacheGet(key);
}
if (om == null)
{
newIds.add(key);
}
else
{
omsMap.put(key, om);
}
}
if (newIds.size() > 0)
{
List<T> newOms = retrieveStoredOMs(newIds);
for (T om : newOms)
{
omsMap.put(om.getPrimaryKey(), om);
if (fromCache)
{
putInstanceImpl(om);
}
}
}
oms = new ArrayList<T>(ids.size());
for (ObjectKey key : ids)
{
oms.add(omsMap.get(key));
}
}
return oms;
}
/**
* Gets a list of om's based on id's.
* This method must be implemented in the derived class
*
* @param ids a <code>List</code> of <code>ObjectKey</code>'s
* @return a <code>List</code> value
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
protected abstract List<T> retrieveStoredOMs(List<? extends ObjectKey> ids)
throws TorqueException;
/**
* Get the cache region used for JCS.
*
* @return the cache region used for JCS.
*/
public String getRegion()
{
return region;
}
/**
* Set the cache region used for JCS.
*
* @param v Value to assign to region.
* @throws TorqueException Any exceptions caught during processing will be
* rethrown wrapped into a TorqueException.
*/
public void setRegion(String v)
throws TorqueException
{
this.region = v;
try
{
if (Torque.getConfiguration().getBoolean(Torque.CACHE_KEY, false))
{
cache = JCS.getInstance(getRegion());
mrCache = new MethodResultCache(cache);
}
else
{
mrCache = new NoOpMethodResultCache(cache);
}
}
catch (CacheException e)
{
throw new TorqueException("Cache could not be initialized", e);
}
if (cache == null)
{
log.info("Cache could not be initialized for region: " + v);
}
}
/**
* @return The cache instance.
*/
public synchronized MethodResultCache getMethodResultCache()
{
if (isNew)
{
registerAsListener();
isNew = false;
}
return mrCache;
}
/**
* NoOp version. Managers should override this method to notify other
* managers that they are interested in CacheEvents.
*/
protected void registerAsListener()
{
// empty
}
/**
*
* @param listener A new listener for cache events.
*/
public void addCacheListenerImpl(CacheListener<? extends Persistent> listener)
{
List<String> keys = listener.getInterestedFields();
for (String key : keys)
{
// Peer.column names are the fields
if (validFields != null && validFields.containsKey(key))
{
FastArrayList listeners = listenersMap.get(key);
if (listeners == null)
{
listeners = createSubsetList(key);
}
boolean isListenerNew = true;
Iterator<?> j = listeners.iterator();
while (j.hasNext())
{
Object listener2 =
((WeakReference<?>) j.next()).get();
// if (listener2 == null)
// {
// // do a little cleanup while checking for dupes
// // not thread-safe, not likely to be many nulls
// // but should revisit
// //j.remove();
// }
// else
if (listener2 == listener)
{
isListenerNew = false;
break;
}
}
if (isListenerNew)
{
listeners.add(new WeakReference<CacheListener<? extends Persistent>>(listener));
}
}
}
}
/**
*
* @param key
* @return A subset of the list identified by <code>key</code>.
*/
private synchronized FastArrayList createSubsetList(String key)
{
FastArrayList list = null;
if (listenersMap.containsKey(key))
{
list = listenersMap.get(key);
}
else
{
list = new FastArrayList();
list.setFast(true);
listenersMap.put(key, list);
}
return list;
}
/**
*
* @param listeners
* @param oldOm
* @param om
*/
protected <TT extends Persistent> void notifyListeners(List<WeakReference<CacheListener<TT>>> listeners,
TT oldOm, TT om)
{
if (listeners != null)
{
synchronized (listeners)
{
Iterator<WeakReference<CacheListener<TT>>> i = listeners.iterator();
while (i.hasNext())
{
CacheListener<TT> listener = i.next().get();
if (listener == null)
{
// remove reference as its object was cleared
i.remove();
}
else
{
if (oldOm == null)
{
// object was added
listener.addedObject(om);
}
else
{
// object was refreshed
listener.refreshedObject(om);
}
}
}
}
}
}
/**
* helper methods for the Serializable interface
*
* @param out
* @throws IOException
*/
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
{
out.defaultWriteObject();
}
/**
* Helper methods for the <code>Serializable</code> interface.
*
* @param in The stream to read a <code>Serializable</code> from.
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
// initialize the cache
try
{
if (region != null)
{
setRegion(region);
}
}
catch (Exception e)
{
log.error("Cache could not be initialized for region '"
+ region + "' after deserialization");
}
}
}