/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.ejb.EJBException;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.GenericEntityObjectFactory;
import org.jboss.ha.framework.interfaces.DistributedState;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.ClusterConfigMetaData;
import org.jboss.metadata.EntityMetaData;
/**
* EntityPersistenceStore implementation storing values in-memory
* and shared accross the cluster through the DistributedState service
* from the clustering framework. It always uses the DefaultPartition.
*
* @see org.jboss.ejb.EntityPersistenceStore
* @see org.jboss.ejb.plugins.CMPInMemoryPersistenceManager
* @see org.jboss.ha.framework.interfaces.DistributedState
*
* @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
* @version $Revision: 81001 $
*/
public class CMPClusteredInMemoryPersistenceManager implements org.jboss.ejb.EntityPersistenceStore
{
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
protected org.jboss.ejb.EntityContainer con = null;
protected Field idField = null;
protected DistributedState ds = null;
protected String DS_CATEGORY = null;
/**
* Optional isModified method used by storeEntity
*/
protected Method isModified = null;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
public CMPClusteredInMemoryPersistenceManager ()
{
}
/**
* This callback is set by the container so that the plugin may access it
*
* @param con The container using this plugin.
*/
public void setContainer (org.jboss.ejb.Container con)
{
this.con = (org.jboss.ejb.EntityContainer)con;
}
/**
* create the service, do expensive operations etc
*/
public void create () throws Exception
{
BeanMetaData bmd = con.getBeanMetaData();
ClusterConfigMetaData ccmd = bmd.getClusterConfigMetaData ();
String partitionName = ccmd.getPartitionName();
String name = "jboss:service=DistributedState,partitionName="+partitionName;
ds = (DistributedState)org.jboss.system.Registry.lookup (name);
String ejbName = bmd.getEjbName();
this.DS_CATEGORY = "CMPClusteredInMemoryPersistenceManager-" + ejbName;
idField = con.getBeanClass ().getField ("id");
try
{
isModified = con.getBeanClass ().getMethod ("isModified", new Class[0]);
if (!isModified.getReturnType ().equals (Boolean.TYPE))
isModified = null; // Has to have "boolean" as return type!
}
catch (NoSuchMethodException ignored)
{
}
}
/**
* start the service, create is already called
*/
public void start () throws Exception
{
}
/**
* stop the service
*/
public void stop ()
{
}
/**
* destroy the service, tear down
*/
public void destroy ()
{
}
// Public --------------------------------------------------------
// EntityPersistenceStore implementation ----------------------------------------------
/**
* Returns a new instance of the bean class or a subclass of the bean class.
*
* @return the new instance
*
* @throws Exception
*/
public Object createBeanClassInstance () throws Exception
{
return con.getBeanClass ().newInstance ();
}
/**
* Initializes the instance context.
*
* <p>This method is called before createEntity, and should
* reset the value of all cmpFields to 0 or null.
*
* @param ctx
*/
public void initEntity (EntityEnterpriseContext ctx)
{
// first get cmp metadata of this entity
Object instance = ctx.getInstance ();
Class ejbClass = instance.getClass ();
Field cmpField;
Class cmpFieldType;
EntityMetaData metaData = (EntityMetaData)con.getBeanMetaData ();
java.util.Iterator i= metaData.getCMPFields ();
while(i.hasNext ())
{
try
{
// get the field declaration
try
{
cmpField = ejbClass.getField ((String)i.next ());
cmpFieldType = cmpField.getType ();
// find the type of the field and reset it
// to the default value
if (cmpFieldType.equals (boolean.class))
{
cmpField.setBoolean (instance,false);
}
else if (cmpFieldType.equals (byte.class))
{
cmpField.setByte (instance,(byte)0);
}
else if (cmpFieldType.equals (int.class))
{
cmpField.setInt (instance,0);
}
else if (cmpFieldType.equals (long.class))
{
cmpField.setLong (instance,0L);
}
else if (cmpFieldType.equals (short.class))
{
cmpField.setShort (instance,(short)0);
}
else if (cmpFieldType.equals (char.class))
{
cmpField.setChar (instance,'\u0000');
}
else if (cmpFieldType.equals (double.class))
{
cmpField.setDouble (instance,0d);
}
else if (cmpFieldType.equals (float.class))
{
cmpField.setFloat (instance,0f);
}
else
{
cmpField.set (instance,null);
}
}
catch (NoSuchFieldException e)
{
// will be here with dependant value object's private attributes
// should not be a problem
}
}
catch (Exception e)
{
throw new EJBException (e);
}
}
}
/**
* This method is called whenever an entity is to be created.
* The persistence manager is responsible for handling the results properly
* wrt the persistent store.
*
* @param m the create method in the home interface that was
* called
* @param args any create parameters
* @param ctx the instance ctx being used for this create call
* @return The primary key computed by CMP PM or null for BMP
*
* @throws Exception
*/
public Object createEntity (Method m, Object[] args,
EntityEnterpriseContext ctx) throws Exception
{
try
{
Object id = idField.get (ctx.getInstance ());
// Check exist
if (this.ds.get (DS_CATEGORY, id.toString ()) != null)
throw new javax.ejb.DuplicateKeyException ("Already exists:"+id);
// Store to file
storeEntity (id, ctx.getInstance ());
return id;
}
catch (IllegalAccessException e)
{
throw new javax.ejb.CreateException ("Could not create entity:"+e);
}
}
/**
* This method is called after the ejbCreate.
* The persistence manager is responsible for handling the results properly
* wrt the persistent store.
*
* @param m the ejbPostCreate method in the bean class that was
* called
* @param args any create parameters
* @param ctx the instance being used for this create call
* @return The primary key computed by CMP PM or null for BMP
*
* @throws Exception
*/
public Object postCreateEntity (Method m, Object[] args,
EntityEnterpriseContext ctx) throws Exception
{
return null;
}
/**
* This method is called when single entities are to be found. The
* persistence manager must find out whether the wanted instance is
* available in the persistence store, if so it returns the primary key of
* the object.
*
* @param finderMethod the find method in the home interface that was
* called
* @param args any finder parameters
* @param instance the instance to use for the finder call
* @return a primary key representing the found entity
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
* @throws javax.ejb.FinderException thrown if some heuristic problem occurs
*/
public Object findEntity (Method finderMethod, Object[] args,
EntityEnterpriseContext instance,
GenericEntityObjectFactory factory) throws Exception
{
if (finderMethod.getName ().equals ("findByPrimaryKey"))
{
if (this.ds.get (DS_CATEGORY, args[0].toString ()) == null)
throw new javax.ejb.FinderException (args[0]+" does not exist");
return factory.getEntityEJBObject(args[0]);
}
else
return null;
}
/**
* This method is called when collections of entities are to be found. The
* persistence manager must find out whether the wanted instances are
* available in the persistence store, and if so it must return a
* collection of primaryKeys.
*
* @param finderMethod the find method in the home interface that was
* called
* @param args any finder parameters
* @param instance the instance to use for the finder call
* @return an primary key collection representing the found
* entities
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
* @throws javax.ejb.FinderException thrown if some heuristic problem occurs
*/
public Collection findEntities (Method finderMethod, Object[] args,
EntityEnterpriseContext instance, GenericEntityObjectFactory factory) throws Exception
{
Collection results = Collections.EMPTY_LIST;
if (finderMethod.getName ().equals ("findAll"))
{
Collection tmpColl = this.ds.getAllKeys (DS_CATEGORY);
if (tmpColl != null)
results = GenericEntityObjectFactory.UTIL.getEntityCollection(factory, tmpColl);
}
return results;
}
/**
* This method is called when an entity shall be activated.
*
* <p>With the PersistenceManager factorization most EJB calls should not
* exists However this calls permits us to introduce optimizations in
* the persistence store. Particularly the context has a
* "PersistenceContext" that a PersistenceStore can use (JAWS does for
* smart updates) and this is as good a callback as any other to set it
* up.
*
* @param instance the instance to use for the activation
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
*/
public void activateEntity (EntityEnterpriseContext instance) { }
/**
* This method is called whenever an entity shall be load from the
* underlying storage. The persistence manager must load the state from
* the underlying storage and then call ejbLoad on the supplied instance.
*
* @param ctx the instance to synchronize
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
*/
public void loadEntity (EntityEnterpriseContext ctx)
{
try
{
// Read fields
byte[] content = (byte[])this.ds.get (this.DS_CATEGORY, ctx.getId ().toString ());
if (content == null)
throw new javax.ejb.EJBException ("No entry exists (any more?) with this id: " + ctx.getId ());
java.io.ObjectInputStream in = new org.jboss.ejb.plugins.CMPClusteredInMemoryPersistenceManager.CMPObjectInputStream (
new java.io.ByteArrayInputStream (content));
Object obj = ctx.getInstance ();
Field[] f = obj.getClass ().getFields ();
for (int i = 0; i < f.length; i++)
{
f[i].set (obj, in.readObject ());
}
in.close ();
}
catch (javax.ejb.EJBException e)
{
throw e;
}
catch (Exception e)
{
throw new EJBException ("Load failed", e);
}
}
/**
* This method is used to determine if an entity should be stored.
*
* @param ctx the instance to check
* @return true, if the entity has been modified
* @throws Exception thrown if some system exception occurs
*/
public boolean isStoreRequired (EntityEnterpriseContext ctx) throws Exception
{
if(isModified == null)
{
return true;
}
Object[] args =
{};
Boolean modified = (Boolean) isModified.invoke (ctx.getInstance (), args);
return modified.booleanValue ();
}
public boolean isModified (EntityEnterpriseContext ctx) throws Exception
{
return isStoreRequired(ctx);
}
/**
* This method is called whenever an entity shall be stored to the
* underlying storage. The persistence manager must call ejbStore on the
* supplied instance and then store the state to the underlying storage.
*B
* @param ctx the instance to synchronize
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
*/
public void storeEntity (EntityEnterpriseContext ctx) throws java.rmi.RemoteException
{
try
{
storeEntity (ctx.getId (), ctx.getInstance ());
}
catch (Exception e)
{
throw new java.rmi.RemoteException (e.toString ());
}
}
/**
* This method is called when an entity shall be passivate. The persistence
* manager must call the ejbPassivate method on the instance.
*
* <p>See the activate discussion for the reason for exposing EJB callback
* calls to the store.
*
* @param instance the instance to passivate
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
*/
public void passivateEntity (EntityEnterpriseContext instance)
{
// This plugin doesn't do anything specific
}
/**
* This method is called when an entity shall be removed from the
* underlying storage. The persistence manager must call ejbRemove on the
* instance and then remove its state from the underlying storage.
*
* @param ctx the instance to remove
*
* @throws java.rmi.RemoteException thrown if some system exception occurs
* @throws javax.ejb.RemoveException thrown if the instance could not be removed
*/
public void removeEntity (EntityEnterpriseContext ctx) throws javax.ejb.RemoveException
{
try
{
if (this.ds.remove (this.DS_CATEGORY, ctx.getId ().toString (), false) == null)
throw new javax.ejb.RemoveException ("Could not remove bean:" +
ctx.getId ());
}
catch (Exception e)
{
throw new javax.ejb.RemoveException (e.toString ());
}
}
// Y overrides ---------------------------------------------------
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
protected void storeEntity (Object id, Object obj) throws Exception
{
try
{
// Store fields
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream ();
java.io.ObjectOutputStream out = new org.jboss.ejb.plugins.CMPClusteredInMemoryPersistenceManager.CMPObjectOutputStream (baos);
Field[] f = obj.getClass ().getFields ();
for (int i = 0; i < f.length; i++)
{
out.writeObject (f[i].get (obj));
}
out.close ();
this.ds.set (this.DS_CATEGORY, id.toString (), baos.toByteArray (), false);
} catch (Exception e)
{
throw new EJBException ("Store failed", e);
}
}
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
static class CMPObjectOutputStream extends java.io.ObjectOutputStream
{
public CMPObjectOutputStream (java.io.OutputStream out) throws IOException
{
super (out);
enableReplaceObject (true);
}
protected Object replaceObject (Object obj)
throws IOException
{
if (obj instanceof javax.ejb.EJBObject)
return ((javax.ejb.EJBObject)obj).getHandle ();
return obj;
}
}
static class CMPObjectInputStream extends java.io.ObjectInputStream
{
public CMPObjectInputStream (java.io.InputStream in) throws IOException
{
super (in);
enableResolveObject (true);
}
protected Object resolveObject (Object obj)
throws IOException
{
if (obj instanceof javax.ejb.Handle)
return ((javax.ejb.Handle)obj).getEJBObject ();
return obj;
}
}
}