/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * This program 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. * * Last commit: $Rev: 1552 $ by $Author: glycoslave $ on $Date:: 2009-07-20 #$ */ /** * $Id: HibernateEntityManager.java 1552 2009-07-19 18:36:56Z glycoslave $ * Last changed $Author: glycoslave $ * EUROCarbDB Project */ package org.eurocarbdb.dataaccess; // stdlib imports import java.util.Set; import java.util.Map; import java.util.List; import java.io.Serializable; // 3rd party imports import org.apache.log4j.Logger; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Hibernate; import org.hibernate.Transaction; import org.hibernate.SessionFactory; import org.hibernate.Criteria; import org.hibernate.cfg.Configuration; import org.hibernate.criterion.Projections; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.NonUniqueObjectException; import org.hibernate.metadata.ClassMetadata; import org.hibernate.hql.QueryTranslator; import org.hibernate.hql.QueryTranslatorFactory; import org.hibernate.engine.SessionFactoryImplementor; // eurocarb imports // import org.eurocarbdb.dataaccess.hibernate.HibernateUtil; import org.eurocarbdb.dataaccess.core.Contributor; import org.eurocarbdb.dataaccess.exception.EurocarbException; import static java.util.Collections.emptyMap; import static org.eurocarbdb.util.StringUtils.join; import static org.eurocarbdb.util.JavaUtils.printStackTrace; /** * Class to handle the management of entities from the database using * the Hibernate API. Includes instantiation, population and * deserialisation from state. * * @author mjh * @author hirenj * @version $Rev: 1552 $ */ public class HibernateEntityManager implements EntityManager { /** Logging handle. */ static final Logger log = Logger.getLogger( HibernateEntityManager.class ); /** Hibernate SessionFactory singleton, created upon request, * in {@link #init()}. */ private static SessionFactory sessionFactory; /** Has a unit of work been started yet? ie: are we inside of a * {@link Transaction}? Hibernate gets very upset if we aren't, * so it's an error to do anything without one. */ private boolean uowStarted = false; /** Default JNDI config file for Eurocarb connection info */ public static final String Default_Jndi_Config_File = "hibernate.jndi.cfg.xml"; //~~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * Init hibernate {@link SessionFactory}, this takes a while, but only * needs to be done once per application instance. */ public static void init() { if ( sessionFactory != null ) throw new RuntimeException( "A non-null SessionFactory already exists"); log.info("initialising hibernate..."); boolean use_jndi = Boolean.valueOf( Eurocarb.getProperty("ecdb.use_jndi") ); // build SessionFactory if ( use_jndi ) { log.info("using JNDI to obtain connection params"); initFromJndi(); } else if ( Eurocarb.getProperty("ecdb.db.username") != null && Eurocarb.getProperty("ecdb.db.password") != null && Eurocarb.getProperty("ecdb.db.name") != null && Eurocarb.getProperty("ecdb.db.hostname") != null ) { log.info("Using application configuration for connection params (ecdb.db.* properties)"); sessionFactory = new Configuration() .configure() // use default 'hibernate.cfg.xml' .setProperty("hibernate.connection.username",Eurocarb.getProperty("ecdb.db.username")) .setProperty("hibernate.connection.password",Eurocarb.getProperty("ecdb.db.password")) .setProperty("hibernate.connection.url","jdbc:postgresql://"+Eurocarb.getProperty("ecdb.db.hostname")+"/"+Eurocarb.getProperty("ecdb.db.name")) .buildSessionFactory(); } else { log.info("using hibernate.properties for connection params"); sessionFactory = new Configuration() .configure() // use default 'hibernate.cfg.xml' .buildSessionFactory(); } // check new SessionFactory validity if ( sessionFactory == null ) throw new RuntimeException( "freshly-created sessionFactory was null"); if ( log.isDebugEnabled() ) { Map<String,ClassMetadata> hash = (Map<String,ClassMetadata>) sessionFactory.getAllClassMetadata(); log.debug("SessionFactory created with " + hash.size() + " entities"); } } static void initFromJndi() { String jndi_conf = Eurocarb.getProperty("ecdb.use_jndi.config"); if ( jndi_conf == null || jndi_conf.length() == 0 ) { log.warn( "JNDI URL not given so reverting to default: " + Default_Jndi_Config_File ); jndi_conf = Default_Jndi_Config_File; } log.info("trying JNDI configuration from file: " + jndi_conf ); sessionFactory = new Configuration() .configure( jndi_conf ) .buildSessionFactory(); log.debug("JNDI config successful"); } /** * Converts the given {@link DetachedCriteria} into a {@link Criteria} * that can be executed. */ public static final Criteria convertToCriteria( DetachedCriteria dc ) { return dc.getExecutableCriteria( getSessionFactory().getCurrentSession() ); } /** * Returns an SQL query string from the given HQL query string. */ public static final String translateHql2Sql( String hql_query_string ) { SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory; QueryTranslatorFactory qtf = sfi.getSettings().getQueryTranslatorFactory(); QueryTranslator qt = qtf.createQueryTranslator( "translated_hql_query" , hql_query_string , emptyMap() , sfi ); qt.compile( emptyMap(), false ); return qt.getSQLString(); } /** * Gets the Hibernate {@link SessionFactory} from which we're * getting our {@link Session}s. */ public static SessionFactory getSessionFactory() { if ( sessionFactory == null ) { log.debug("sessionFactory == null, dynamically initialising one..."); init(); } return sessionFactory; } /** * Rolls back current transaction, doesn't close Hibernate {@link Session}. * {@inheritDoc} */ public void abortUnitOfWork() { /* if ( ! uowStarted ) throw new EurocarbException( "Unit of work not yet started, cannot abort"); */ Transaction tx = getHibernateSession().getTransaction(); if ( tx == null ) return; if ( ! tx.isActive() ) { log.warn("a transaction is not active, cannot be aborted"); return; } try { log.warn("aborting current transaction, trying to rollback..."); tx.rollback(); log.debug("transaction successfully rolled back"); } catch ( Throwable ex ) { log.error( "Could not rollback transaction - " + "exception caught during rollback:", ex ); } finally { uowStarted = false; } } /** * Opens new (Hibernate-wrapped) transaction. * {@inheritDoc} */ public void beginUnitOfWork() { log.debug("starting database transaction"); getHibernateSession().beginTransaction(); uowStarted = true; } /** * Commits current transaction, rolls back if commit throws Exception. * {@inheritDoc} */ public void endUnitOfWork() { boolean success = false; try { if ( getHibernateSession().getTransaction().isActive() ) { log.debug("*** committing database transaction ***"); getHibernateSession().getTransaction().commit(); } else { log.warn("no active transaction, cannot be ended"); } success = true; } finally { if ( ! success ) abortUnitOfWork(); log.trace("(closing hibernate session)"); getHibernateSession().close(); uowStarted = false; } } /** * Returns the current Hibernate {@link Session}, or creates one if necessary. * @see {@link SessionFactory} */ public Session getHibernateSession() { return getSessionFactory().getCurrentSession(); } /* @see EntityManager#countAll(Class) */ public <T> int countAll( Class<T> c ) { if ( log.isTraceEnabled() ) log.trace("looking up total count of objects of " + c ); Object count = getHibernateSession() .createCriteria( c ) .setProjection( Projections.rowCount() ) .uniqueResult(); if ( count == null ) return 0; if ( count instanceof Integer ) { return ((Integer) count).intValue(); } else if ( count instanceof Long ) { return ((Long) count).intValue(); } else return 0; } /* @see EntityManager#createNew(Class) */ public <T> T createNew( Class<T> c ) { return __instance_of( c ); } /* @see EntityManager#flush() */ public void flush() { log.trace("attempting to manually flush data to data store."); if ( getHibernateSession().getTransaction().isActive() ) { log.debug("*** manually flushing data to data store ***"); getHibernateSession().flush(); } else { log.warn("no active transaction, cannot flush data to store."); } } /* @see EntityManager#createQuery(Class) */ public <T> Criteria createQuery( Class<T> c ) { return getHibernateSession().createCriteria( c ); } /* @see EntityManager#getQuery(String) */ public Query getQuery( String name_of_query ) { return getHibernateSession().getNamedQuery( name_of_query ); } /* @see EntityManager#lookup(Class,Serializable) */ @SuppressWarnings("unchecked") // ^^^ unavoidable, due to lack of genericity in Hibernate lib public <T> T lookup( Class<T> c, int object_id ) { if ( log.isDebugEnabled() ) log.debug( "attempting to lookup object of " + c.getName() + " with id=" + object_id ); try { Session session = getHibernateSession(); T entity = (T) session.get( c, object_id ); return entity; } catch ( NonUniqueObjectException e ) { __log_exception( e, c ); throw e; } } /** * Attempts to pre-fetch most of the data for the given object, including * each of its properties and associations. If the passed object is a * {@link Collection} then the whole Collection will be pre-fetched. */ public <T> void populate( T object ) { Hibernate.initialize( object ); } /* @see EntityManager#lookup(T,Serializable) */ public <T> void lookup( T destinationObject, int objectId ) { if ( log.isDebugEnabled() ) { log.debug( "attempting to populate existing object " + destinationObject + " with id=" + objectId ); } try { Session session = getHibernateSession(); session.load(destinationObject, objectId); } catch ( NonUniqueObjectException e ) { __log_exception( e, destinationObject ); throw e; } } public <T> void revert( T entity ) { Session s = getHibernateSession(); s.evict(entity); } /* @see EntityManager#remove(T) */ public <T> void remove( T entity ) { if ( log.isDebugEnabled() ) log.debug( "attempting to remove (detach) object " + entity ); Session s = getHibernateSession(); s.delete(entity); } /* @see EntityManager#removeAll */ public <T> void removeAll( Set<? extends T> entities ) { for ( T e : entities ) remove( e ); } /* @see EntityManager#store(T) */ public <T> void store( T entity ) { if ( log.isDebugEnabled() ) log.debug( "attempting to store (make persistent) object " + entity ); Session s = getHibernateSession(); if ( entity instanceof EurocarbObject ) validate( (EurocarbObject) entity ); try { s.save( entity ); //s.saveOrUpdate( entity ); } catch ( NonUniqueObjectException ex ) { log.warn( "passed " + entity.getClass().getSimpleName() + " was originally loaded in a different Session" + "; attempting to merge changes..." , ex ); s.merge( entity ); log.debug( "changes merged ok" ); } } /** * Checks that the passed {@link EurocarbObject} is valid, throws * exceptions if it isn't. */ void validate( EurocarbObject x ) throws EurocarbException { try { x.validate(); } catch ( EurocarbException ex ) { log.warn("Caught exception while validating " + x, ex ); throw ex; } validateContributor(x); } private void validateContributor( Object x ) throws EurocarbException { return; } private void validateContributor( Contributed x ) throws EurocarbException { if (x.getContributor() == null) { throw new EurocarbException("Could not validate EurocarbObject, contributor is null"); } } /* @see EntityManager#update(T) */ public <T> void update( T entity ) { if ( log.isDebugEnabled() ) log.debug("attempting to update object " + entity ); Session s = getHibernateSession(); s.update( entity ); } public <T> void refresh( T entity ) { if ( log.isDebugEnabled() ) log.debug( "attempting to refresh object of " + entity.getClass() ); Session s = getHibernateSession(); s.refresh( entity ); } @Deprecated public void refreshContributor(Contributed entity) { if( entity==null || entity.getContributor()==null || entity.getContributor().getContributorId()<0 ) return; Contributor contributor = lookup(Contributor.class,entity.getContributor().getContributorId()); if( contributor!=null ) entity.setContributor(contributor); } public <T> void merge( T entity ) { if ( log.isDebugEnabled() ) log.debug( "attempting to merge object of " + entity.getClass() ); Session s = getHibernateSession(); s.merge( entity ); } public <T> void storeAll( Set<? extends T> entities ) { for ( T e : entities ) store( e ); } /* instantiates an instance of passed class; absorbs exceptions. */ private static final <T> T __instance_of( Class<T> c ) { T entity = null; try { entity = c.newInstance(); } catch ( InstantiationException e ) { e.printStackTrace(); } catch ( IllegalAccessException e ) { e.printStackTrace(); } assert entity != null; return entity; } private final void __log_exception( NonUniqueObjectException e, Object entity ) { log.warn( "Caught NonUniqueObjectException while working with " + entity.getClass().getName() + ". This often means that you need to (re-)implement " + "the equals(Object) & hashCode() methods in this class " + "so that objects in the session can be compared for " + "*equality* rather than *identity*.", e ); } } // end class