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

Reply via email to