In the process of getting Middlegen's airline sample to work
(sort of, except for Struts), I developed a new SessionFactoryObjectFactory
which actually works with Tomcat's JNDI resources. None of the
stuff in the docs or found from google were satisfactory, e.g.
http://www.hibernate.org/114.html
http://www.hibernate.org/133.html
http://www.hibernate.org/105.html
altho they provided good starting points.
Instead I extended/finished net.sf.hibernate.impl.SessionFactoryObjectFactory
(is that finished/does it really work somewhere?). I changed getObjectInstance()
to call a method createInstance() if the factory does not exist.
I called my class BasicSessionFactoryObjectFactory since I started by
looking at what org.apache.commons.dbcp.BasicDataSourceFactory does.
And I'm not sure what my changes would do to other containers.
BasicSessionFactoryObjectFactory will configure hibernate from
individual ResourceParms, or from a configFilePath specified as
a ResourceParm.
I've included rather than attached the files below (no diffs so you
can play with both ObjectFactory's).
Code bits were copied from/inspired by BasicDataSourceFactory,
hibernate's Configuration, HibernateService classes, and the
JndiSessionFactoryImpl in the docs.
I still have one "XXX" spot that I don't what to do with -
which properties to send to addInstance(). Also, I know that
SessionFactoryImpl ends up calling the original SessionFactoryObjectFactory
at some points, but this seems to work.
oh, I'm using Hibernate 2.1 and Tomcat 5.0.18.
I'd appreciate any comments/suggestions/corrections.
(I also briefly explored getting MBeans to work with Tomcat but
this looked easier to understand/get working. Basically I added
a call to NamingHelper.bind() to HibernateService but then I ran
into other JMX problems and switched to ObjectFactory.)
John Allison
[EMAIL PROTECTED]
Here's what I put in the Context element of my webapp .xml file:
<Resource name="AirlineHibernateFactory" type="net.sf.hibernate.SessionFactory"/>
<ResourceParams name="AirlineHibernateFactory">
<parameter>
<name>factory</name>
<value>airline.BasicSessionFactoryObjectFactory</value>
</parameter>
<!-- configFilePath is relative to "/WEB-INF/classes" and must start with '/'
-->
<!-- if configFilePath is specified, then it's used and the other parameters
are ignored -->
<!--
<parameter>
<name>configFilePath</name>
<value>/hibernate.cfg.xml</value>
</parameter>
-->
<!-- MapResources is ',' separated list of .hbm.xml files, relative to
"/WEB-INF/classes" and each must start with '/' -->
<parameter>
<name>MapResources</name>
<value>/airline/hibernate/Flight.hbm.xml,/airline/hibernate/Reservation.hbm.xml,/airline/hibernate/Person.hbm.xml</value>
</parameter>
<parameter>
<name>connection.datasource</name>
<value>java:comp/env/jdbc/airline</value>
</parameter>
<parameter>
<name>dialect</name>
<value>net.sf.hibernate.dialect.MySQLDialect</value>
</parameter>
<parameter>
<name>use_outer_join</name>
<value>true</value>
</parameter>
<parameter>
<name>show_sql</name>
<value>true</value>
</parameter>
<parameter>
<name>transaction.factory_class</name>
<value>net.sf.hibernate.transaction.JDBCTransactionFactory</value>
</parameter>
</ResourceParams>
And here's the Java source, which I compiled and put under /WEB-INF/classes:
//$Id$
package airline;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Enumeration;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.RefAddr;
import javax.naming.event.EventContext;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingExceptionEvent;
import javax.naming.event.NamingListener;
import javax.naming.spi.ObjectFactory;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.util.FastHashMap;
import net.sf.hibernate.util.NamingHelper;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.cfg.Environment;
import net.sf.hibernate.util.PropertiesHelper;
import net.sf.hibernate.MappingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Resolves <tt>SessionFactory</tt> JNDI lookups and deserialization
* @see net.sf.hibernate.impl.SessionFactoryObjectFactory
* @see org.apache.commons.dbcp.BasicDataSourceFactory
* @see net.sf.hibernate.cfg.Configuration;
* @author John Allison - finished implementation by actually creating SessionFactory's
*/
public class BasicSessionFactoryObjectFactory implements ObjectFactory {
private static final BasicSessionFactoryObjectFactory INSTANCE; //to stop the
class from being unloaded
private static final Log log;
static {
log = LogFactory.getLog(BasicSessionFactoryObjectFactory.class);
INSTANCE = new BasicSessionFactoryObjectFactory();
log.debug("initializing class BasicSessionFactoryObjectFactory");
}
private static final FastHashMap INSTANCES = new FastHashMap();
private static final FastHashMap NAMED_INSTANCES = new FastHashMap();
private static final NamingListener LISTENER = new NamespaceChangeListener() {
public void objectAdded(NamingEvent evt) {
log.debug( "A factory was successfully bound to name: " +
evt.getNewBinding().getName() );
}
public void objectRemoved(NamingEvent evt) {
String name = evt.getOldBinding().getName();
log.info("A factory was unbound from name: " + name);
Object instance = NAMED_INSTANCES.remove(name);
Iterator iter = INSTANCES.values().iterator();
while ( iter.hasNext() ) {
if ( iter.next()==instance ) iter.remove();
}
}
public void objectRenamed(NamingEvent evt) {
String name = evt.getOldBinding().getName();
log.info("A factory was renamed from name: " + name);
NAMED_INSTANCES.put( evt.getNewBinding().getName(),
NAMED_INSTANCES.remove(name) );
}
public void namingExceptionThrown(NamingExceptionEvent evt) {
log.warn( "Naming exception occurred accessing factory: " +
evt.getException() );
}
};
private static String[] parseResourceList(String resourceList) {
return PropertiesHelper.toStringArray(resourceList, " ,\n\t\r\f");
}
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable
env) throws Exception {
log.error("getObjectInstance: " + obj + " " + name);
if ((obj == null) || !(obj instanceof Reference)) {
return (null);
}
Reference reference = (Reference) obj;
if
(!"net.sf.hibernate.SessionFactory".equals(reference.getClassName())) {
return (null);
}
log.warn("JNDI lookup: " + name);
String uid = (String) reference.get(0).getContent();
Object instance = getInstance(uid);
if (instance == null) {
try {
instance = createInstance(reference,name,ctx,env);
}
catch (HibernateException he) {
log.error( "Could not build SessionFactory: " +
he.getMessage() );
log.debug("Error was", he);
return null;
}
// XXX Properties : use env ? or something from ctx ?
addInstance(uid,name.toString(),(SessionFactory)instance,new
Properties());
}
return instance;
}
public static SessionFactory createInstance(Reference reference, Name name,
Context ctx, Hashtable env) throws Exception {
SessionFactory sf = null;
RefAddr ra = null;
log.info("createInstance for " + name);
Configuration cf = new Configuration();
if (cf == null) {
throw new HibernateException("can't allocate new
Configuration");
}
ra = reference.get("configFilePath");
if (ra != null) {
Object value = ra.getContent();
if (value == null) {
throw new HibernateException("configFilePath content
is null");
}
String resource = value.toString();
log.info("configFilePath="+resource);
if (resource.length() == 0) {
throw new HibernateException("configFilePath parameter
is empty");
}
return cf.configure(resource).buildSessionFactory();
}
// populate properties from ResourceParams
// don't want to directly cf.setProperty() so that we can fiddle with
mapresources and also check for empty and fall-thru to cf.configure()
Properties props = new Properties();
props.setProperty(Environment.SESSION_FACTORY_NAME,name.toString());
for (Enumeration e = reference.getAll() ; e.hasMoreElements() ;) {
ra = (RefAddr)e.nextElement();
String type = ra.getType().toString().trim(); // do I need
trim()?
String cont = ra.getContent().toString().trim();
if (!type.equalsIgnoreCase("factory") &&
!type.equalsIgnoreCase("scope")) {
// skip factory, that's this class; scope is for jndi
props.setProperty(type,cont);
if ( !type.startsWith("hibernate") )
props.setProperty("hibernate." + type, cont);
}
}
// add MapResources to Configuration individually
String mapres = (String)props.remove("MapResources");
if (mapres != null) {
String[] mappingFiles = parseResourceList(mapres);
for ( int i=0; i<mappingFiles.length; i++ ) {
try {
cf.addResource( mappingFiles[i],
Thread.currentThread().getContextClassLoader() );
}
catch (MappingException me) {
cf.addResource( mappingFiles[i],
Environment.class.getClassLoader() );
}
}
}
if (!props.isEmpty()) {
log.info("configuring hibernate with properties from
ResourceParams");
log.info("properties = " + props);
cf.addProperties(props); // don't use setProperties() so we
can keep the defaults for properties not specified as ResourceParms
return cf.buildSessionFactory();
}
// fall-thru and attempt default configuration
log.info("configuring hibernate with default");
return cf.configure().buildSessionFactory();
}
public static void addInstance(String uid, String name, SessionFactory
instance, Properties properties) {
log.debug("registered: " + uid + " (" + ( (name==null) ? "unnamed" :
name ) + ')');
INSTANCES.put(uid, instance);
if (name!=null) NAMED_INSTANCES.put(name, instance);
//must add to JNDI _after_ adding to HashMaps, because some JNDI
servers use serialization
if (name==null) {
log.info("no JNDI name configured");
}
else {
log.info("Factory name: " + name);
try {
Context ctx =
NamingHelper.getInitialContext(properties);
NamingHelper.bind(ctx, name, instance);
log.info("Bound factory to JNDI name: " + name);
( (EventContext) ctx ).addNamingListener(name,
EventContext.OBJECT_SCOPE, LISTENER);
}
catch (InvalidNameException ine) {
log.error("Invalid JNDI name: " + name, ine);
}
catch (NamingException ne) {
log.warn("Could not bind factory to JNDI", ne);
}
catch(ClassCastException cce) {
log.warn("InitialContext did not implement
EventContext");
}
}
}
public static void removeInstance(String uid, String name, Properties
properties) {
//TODO: theoretically non-threadsafe...
if (name!=null) {
log.info("Unbinding factory: " + name);
try {
Context ctx =
NamingHelper.getInitialContext(properties);
ctx.unbind(name);
log.info("Unbound factory from JNDI name: " + name);
}
catch (InvalidNameException ine) {
log.error("Invalid JNDI name: " + name, ine);
}
catch (NamingException ne) {
log.warn("Could not unbind factory from JNDI", ne);
}
NAMED_INSTANCES.remove(name);
}
INSTANCES.remove(uid);
}
public static Object getNamedInstance(String name) {
log.warn("lookup: name=" + name);
Object result = NAMED_INSTANCES.get(name);
if (result==null) {
log.warn("Not found: " + name);
log.warn(NAMED_INSTANCES);
}
return result;
}
public static Object getInstance(String uid) {
log.debug("lookup: uid=" + uid);
Object result = INSTANCES.get(uid);
if (result==null) {
log.warn("Not found: " + uid);
log.debug(INSTANCES);
}
return result;
}
}
//end of file
-------------------------------------------------------
This SF.Net email is sponsored by: IBM Linux Tutorials
Free Linux tutorial presented by Daniel Robbins, President and CEO of
GenToo technologies. Learn everything from fundamentals to system
administration.http://ads.osdn.com/?ad_id=1470&alloc_id=3638&op=click
_______________________________________________
hibernate-devel mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/hibernate-devel