<?xml version="1.0"?>
<!--
 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.
-->

<document>

  <properties>
    <title>Hibernate Howto</title>
  </properties>

  <body>

    <section name="Introduction">
      <p>
        This is a HOWTO on implementing Hibernate as the database OM layer.  The motivating factors for using Hibernate
        are:
        <ol>
          <li>
      You just use POJO (Plain Old Java Objects) versus generated objects.
          </li>
          <li>
      You can either start with Java objects and create your database schema from there, or
      start with a database schema, and map the Java objects to the schema.
          </li>
          <li>
      Extensive performance optimizations built into the Hibernate engine.
          </li>
          <li>
      Leverage Avalon component use in Turbine 2.3!
          </li>
        </ol>
      </p>
      <p>
        Please review the content available from the Hibernate homepage.  This howto assumes
        you already are confortable with the various Hibernate concepts.
      </p>
      <p>
    In this example, we will take a simple schema with a single table and integrate Hibernate into
    a Turbine action.  We will talk about best practices for Hibernate and Turbine.
        <ol>
          <li>
        Configure project dependencies.
          </li>
          <li>
        Directory Structures
          </li>

          <li>
        Setup Avalon component configuration.
          </li>

          <li>
          Setup Hibernate mapping files.
          </li>
          <li>
          Create static ServiceLocator class.
          </li>
          <li>
            Create filter that manages the Hibernate Session.
          </li>
          <li>
            Setup PersitenceException handling
          </li>
        </ol>
      </p>

    </section>

    <section name="Directory Structures">
      <p>
      While you can organize your directory structure however you like, I tend to have structure like:
      </p>
      <source>
      /src/java/com/my/project/bizobj business objects reside
      /src/java/com/my/project/om POJO objects mapped to database reside
      /src/java/com/my/project/om/persist manager objects that faciliate retrieve POJO objects
      /src/java/com/my/project/om/filter Servlet Filter lives that maintains the Hibernate Session
      /src/java/ where hibernate.hbm.xml and hibernate.cfg.xml files live

    </source>

    </section>

    <section name="Configure project dependencies" >
      <p>
        The easiest way to maintain a project is to use Maven.  Here are the sample
        dependencies for your project.xml.
      </p>
      <source>
<![CDATA[
    <dependency>
      <id>hibernate</id>
      <version>2.0-beta6</version>
      <properties>
      <war.bundle.jar>true</war.bundle.jar>
      </properties>
    </dependency>
    <dependency>
      <id>hibernate:hibernate-avalon</id>
      <version>0.1</version>
      <properties>
      <war.bundle.jar>true</war.bundle.jar>
      </properties>
    </dependency>
    <dependency>
      <id>dom4j</id>
      <version>1.4</version>
      <properties>
      <war.bundle.jar>true</war.bundle.jar>
      </properties>
    </dependency>
    <dependency>
      <id>cglib</id>
      <version>rc2-1.0</version>
      <properties>
      <war.bundle.jar>true</war.bundle.jar>
      </properties>
    </dependency>
    <dependency>
      <id>jcs</id>
      <version>1.0-dev</version>
      <properties>
      <war.bundle.jar>true</war.bundle.jar>
      </properties>
    </dependency>
]]>
      </source>
      <p>
        The key jar file is the hibernate-avalon.jar file, it contains the Avalon wrapper
        for Hibernate, and can be used with any Avalon container.
      </p>

    </section>

    <section name="Setting up Avalon Configuration" >

        <p>
      We leverage the Avalon based HibernateService that is hosted as part of the
      HibernateExt project.  You must specify the name of the service, as well as the implementing
      service in your componentConfiguration.xml file.  Other configuration parameters can be passed
      in as well.
        </p>
      <source>
<![CDATA[

<my-system>
  <component
    role="net.sf.hibernate.avalon.HibernateService"
    class="net.sf.hibernate.avalon.HibernateServiceImpl">
  </component>
</my-system>

]]>
      </source>

        <p>
      The componentRoles file also defines how to access the component.  Here is a
      very simple componentRoles.xml file:
        </p>
      <source>
<![CDATA[

<role-list>
 <role
    name="net.sf.hibernate.avalon.HibernateService"
    shorthand="hibernate"
    default-class="net.sf.hibernate.avalon.HibernateServiceImpl"/>
</role-list>

]]>
      </source>
    </section>



    <section name="Setup Hibernate mapping files">
      <p>
    This section will not go into depth on how to configure the hibernate.cfg.xml and
    hibernate.hbm.xml files.  However, if you don't specify in the componentConfiguration.xml
    file where they are located, then they should be placed in your src/java/ directory.
      </p>
      <p>
        The exact configuration is dependent on the environment Turbine is running in, however
        I highly recommend that you use the JNDI look up of the datasource, and use DBCP for your
        connection pooling.  I have found that when using Hibernate in cactus tests, you may need to
        increase the pool size required in order to not run out of connections, however this doesn't
        seem to apply to running as a web application.
      </p>
    </section>



    <section name="Create static ServiceLocator class.">
      <p>
    Currently this class is not integrated into Turbine, so you will need to provide an implemation yourself.
    This provides a static method of retrieving your Hibernate Session.
      </p>

        <source>
<![CDATA[
package com.upstate.cellculture.om.persist;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.JDBCException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class is used to get Hibernate Sessions and may
 * also contain methods (in the future) to get DBConnections
 * or Transactions from JNDI.
 */
public class ServiceLocator
{
    //~ Static fields/initializers =============================================
    public final static String SESSION_FACTORY = "hibernate/sessionFactory";
    public static final ThreadLocal session = new ThreadLocal();
    private static SessionFactory sf = null;
    private static ServiceLocator me;
    private static Log log = LogFactory.getLog(ServiceLocator.class);

    static {
        try
        {
            me = new ServiceLocator();
        }
        catch (Exception e)
        {
            log.fatal("Error occurred initializing ServiceLocator");
            e.printStackTrace();
        }
    }

    //~ Constructors ===========================================================

    private ServiceLocator() throws HibernateException, JDBCException
    {}

    //~ Methods ================================================================

    public static Session currentSession() throws PersistenceException
    {
        Session s = (Session) session.get();

        if (s == null)
        {
            s = PersistenceManager.openSession();
            if (log.isDebugEnabled())
            {
                log.debug("Opened hibernate session.");
            }

            session.set(s);
        }

        return s;
    }

    public static void closeSession() throws HibernateException, JDBCException
    {
        Session s = (Session) session.get();
        session.set(null);

        if (s != null)
        {
            if (s.isOpen())
            {
                s.flush();
                s.close();

                if (log.isDebugEnabled())
                {
                    log.debug("Closed hibernate session.");
                }
            }
        }
        else
        {
            log.warn("Hibernate session was inadvertently already closed.");

        }
    }
}
]]>
      </source>
    </section>

    <section name="Create Servlet Filter.">
      <p>
        The servlet Filter is required if you use lazy loading of objects because you need to open a Hibernate sesion, and keeps it open through the view layer.
        This ensures that each user get's their own Hibernate Session.  ?? Should this be done
        through some sort of SessionValidator?

      </p>

        <source>
<![CDATA[
package com.upstate.cellculture.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.sf.hibernate.Session;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.upstate.cellculture.om.persist.PersistenceException;
import com.upstate.cellculture.om.persist.ServiceLocator;

public class ActionFilter implements Filter
{
    //~ Static fields/initializers =============================================

    //~ Instance fields ========================================================

    /**
     * The <code>Log</code> instance for this class
     */
    private Log log = LogFactory.getLog(ActionFilter.class);
    private FilterConfig filterConfig = null;

    //~ Methods ================================================================

    public void init(FilterConfig filterConfig) throws ServletException
    {
        this.filterConfig = filterConfig;

    }

    /**
     * Destroys the filter.
     */
    public void destroy()
    {
        filterConfig = null;
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException
    {
        // cast to the types I want to use
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpSession session = request.getSession(true);

        Session ses = null;
        boolean sessionCreated = false;

        try
        {
            chain.doFilter(request, response);
        }
        finally
        {
            try
            {
                ServiceLocator.closeSession();
            }
            catch (Exception exc)
            {
                log.error("Error closing hibernate session.", exc);
                exc.printStackTrace();
            }
        }
    }

    public static Session getSession() throws PersistenceException
    {
        try
        {

            return ServiceLocator.currentSession();
        }
        catch (Exception e)
        {
            throw new PersistenceException("Could not find current Hibernate session.", e);
        }

    }
}

]]>
      </source>
    </section>






    <section name="Implementing Manager classes">
        <p>
      To centralize access to the database, we create Manager classes.  Often Manager
      classes are called DAO (Data Access Classes).  Here is an example:
        </p>
        <source>
<![CDATA[
/*
 * Created on Apr 28, 2003
 *
 */
package com.upstate.cellculture.om.persist;
import java.util.List;

import net.sf.hibernate.Query;
import net.sf.hibernate.Session;

import com.upstate.cellculture.om.Technician;
/**
 * @author tmckinney
 *
 * Centralizes all access to the Technicians table
 */
public class TechnicianManager
{
    private static List allTechnicians;
    public static void save(Technician technician) throws PersistenceException
    {
        try
        {

            ServiceLocator.currentSession().save(technician);

        }
        catch (Exception e)
        {
            throw new PersistenceException("Could not save.", e);
        }
    }

    public static Technician retrieveByPK(long technicianId) throws PersistenceException
       {
           try
             {
                 Technician technician= (Technician) ServiceLocator.currentSession().load(Technician.class, new Long(technicianId));
                 return technician;
             }
             catch (Exception e)
             {
                 throw new PersistenceException("Could not retrieve.", e);
             }
       }


    public static List retrieveAllTechnicians() throws PersistenceException
    {
        if (allTechnicians == null)
        {
            try
            {
                Query q = ServiceLocator.currentSession().createQuery("from technician in class " + Technician.class +" order by upper(technician.name)");
                allTechnicians = q.list();
                session.flush();
            }
            catch (Exception e)
            {
                e.printStackTrace();
                throw new PersistenceException(e);
            }
        }
        return allTechnicians;

    }


}

]]>
      </source>
      <p>
          By using this TechnicianManager class, we could feasibly swap out the Hibernate code
          for another OM strategy.  Even better would be to have a TechnicianManager interface
          and a TechicianManagerImpl class.  Possibly loaded via Avalon as a component!
      </p>
    </section>

    <section name="Setup PersitenceException handling">
        <p>
      By catching a generice Persistence Exception that wraps all the
      thrown exceptions we can just catch a single exception and then
      retrieve what type of exception it is.
        </p>
        <source>
<![CDATA[
package com.upstate.cellculture.om.persist;

import org.apache.commons.lang.exception.NestableException;
/**
 * A general PersistenceException that is thrown by all Manager classes.
 *
 */
public class PersistenceException extends NestableException
{
    //~ Constructors ===========================================================

    /**
     * Constructor for PersistenceException.
     */
    public PersistenceException()
    {
        super();
    }

    /**
     * Constructor for PersistenceException.
     *
     * @param message
     */
    public PersistenceException(String message)
    {
        super(message);
    }

    /**
     * Constructor for PersistenceException.
     *
     * @param message
     * @param cause
     */
    public PersistenceException(String message, Throwable cause)
    {
        super(message, cause);
    }

    /**
     * Constructor for PersistenceException.
     *
     * @param cause
     */
    public PersistenceException(Throwable cause)
    {
        super(cause);
    }

}

]]>
      </source>

    </section>
  </body>
</document>


