Java tutorial
/* ************************************************************************* * The contents of this file are subject to the Openbravo Public License * Version 1.1 (the "License"), being the Mozilla Public License * Version 1.1 with a permitted attribution clause; you may not use this * file except in compliance with the License. You may obtain a copy of * the License at http://www.openbravo.com/legal/license.html * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * The Original Code is Openbravo ERP. * The Initial Developer of the Original Code is Openbravo SLU * All portions are Copyright (C) 2008-2014 Openbravo SLU * All Rights Reserved. * Contributor(s): ______________________________________. ************************************************************************ */ package org.openbravo.dal.core; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; import org.apache.log4j.Logger; import org.hibernate.FlushMode; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.openbravo.base.model.Entity; import org.openbravo.base.provider.OBNotSingleton; import org.openbravo.base.provider.OBProvider; import org.openbravo.base.session.OBPropertiesProvider; import org.openbravo.base.session.SessionFactoryController; import org.openbravo.base.structure.BaseOBObject; import org.openbravo.base.structure.Identifiable; import org.openbravo.base.util.Check; import org.openbravo.dal.service.OBDal; import org.openbravo.database.ExternalConnectionPool; /** * Keeps the Hibernate Session and Transaction in a ThreadLocal so that it is available throughout * the application. This class provides convenience methods to get a Session and to * create/commit/rollback a transaction. * * @author mtaal */ // TODO: revisit when looking at factory pattern and dependency injection // framework public class SessionHandler implements OBNotSingleton { private static final Logger log = Logger.getLogger(SessionHandler.class); private static ExternalConnectionPool externalConnectionPool; { String poolClassName = OBPropertiesProvider.getInstance().getOpenbravoProperties() .getProperty("db.externalPoolClassName"); if (poolClassName != null) { try { externalConnectionPool = ExternalConnectionPool.getInstance(poolClassName); } catch (Throwable e) { externalConnectionPool = null; log.warn("External connection pool class not found: " + poolClassName, e); } } } // The threadlocal which handles the session private static ThreadLocal<SessionHandler> sessionHandler = new ThreadLocal<SessionHandler>(); /** * Removes the current SessionHandler from the ThreadLocal. A call to getSessionHandler will * create a new SessionHandler, session and transaction. */ public static void deleteSessionHandler() { log.debug("Removing sessionhandler"); sessionHandler.set(null); } /** @return true if a session handler is present for this thread, false */ public static boolean isSessionHandlerPresent() { return sessionHandler.get() != null; } /** * Returns the SessionHandler of this thread. If there is none then a new one is created and a * Hibernate Session is created and a transaction is started. * * @return the sessionhandler for this thread */ public static SessionHandler getInstance() { SessionHandler sh = sessionHandler.get(); if (sh == null) { log.debug("Creating sessionHandler"); sh = getCreateSessionHandler(); sh.begin(); sessionHandler.set(sh); } return sh; } private static boolean checkedSessionHandlerRegistration = false; private static SessionHandler getCreateSessionHandler() { if (!checkedSessionHandlerRegistration && !OBProvider.getInstance().isRegistered(SessionHandler.class)) { OBProvider.getInstance().register(SessionHandler.class, SessionHandler.class, false); } return OBProvider.getInstance().get(SessionHandler.class); } private Session session; private Transaction tx; private Connection connection; // Sets the session handler at rollback so that the controller can rollback // at the end private boolean doRollback = false; /** @return the session */ public Session getSession() { return session; } protected void setSession(Session thisSession) { session = thisSession; } public void setConnection(Connection connection) { this.connection = connection; } public Connection getConnection() { return this.connection; } protected Session createSession() { // Checks if the session connection has to be obtained using an external connection pool if (externalConnectionPool != null && this.getConnection() == null) { Connection externalConnection = externalConnectionPool.getConnection(); this.setConnection(externalConnection); } if (this.connection != null) { // If the connection has been obtained using an external connection pool it is passed to // openSession, to prevent a new connection to be created using the Hibernate default // connection pool return SessionFactoryController.getInstance().getSessionFactory().openSession(this.connection); } else { return SessionFactoryController.getInstance().getSessionFactory().openSession(); } } protected void closeSession() { session.close(); } /** * Saves the object in this getSession(). * * @param obj * the object to persist */ public void save(Object obj) { if (Identifiable.class.isAssignableFrom(obj.getClass())) { getSession().saveOrUpdate(((Identifiable) obj).getEntityName(), obj); } else { getSession().saveOrUpdate(obj); } } /** * Delete the object from the db. * * @param obj * the object to remove */ public void delete(Object obj) { if (Identifiable.class.isAssignableFrom(obj.getClass())) { getSession().delete(((Identifiable) obj).getEntityName(), obj); } else { getSession().delete(obj); } } /** * Queries for a certain object using the class and id. If not found then null is returned. * * @param clazz * the class to query * @param id * the id to use for querying * @return the retrieved object, can be null */ @SuppressWarnings("unchecked") public <T extends Object> T find(Class<T> clazz, Object id) { // translates a class to an entityname because the hibernate // getSession().get method can not handle class names if the entity was // mapped with entitynames. if (Identifiable.class.isAssignableFrom(clazz)) { return (T) find(DalUtil.getEntityName(clazz), id); } return (T) getSession().get(clazz, (Serializable) id); } /** * Queries for a certain object using the entity name and id. If not found then null is returned. * * @param entityName * the name of the entity to query * @param id * the id to use for querying * @return the retrieved object, can be null * * @see Entity */ public BaseOBObject find(String entityName, Object id) { return (BaseOBObject) getSession().get(entityName, (Serializable) id); } /** * Create a query object from the current getSession(). * * @param qryStr * the HQL query * @return a new Query object */ public Query createQuery(String qryStr) { return getSession().createQuery(qryStr); } /** * Starts a transaction. */ protected void begin() { Check.isTrue(getSession() == null, "Session must be null before begin"); setSession(createSession()); getSession().setFlushMode(FlushMode.COMMIT); Check.isTrue(tx == null, "tx must be null before begin"); tx = getSession().beginTransaction(); log.debug("Transaction started"); } /** * Commits the transaction and closes the session, should normally be called at the end of all the * work. */ public void commitAndClose() { boolean err = true; try { Check.isFalse(TriggerHandler.getInstance().isDisabled(), "Triggers disabled, commit is not allowed when in triggers-disabled mode, " + "call TriggerHandler.enable() before committing"); checkInvariant(); flushRemainingChanges(); if (connection == null || (connection != null && !connection.isClosed())) { if (connection != null) { connection.setAutoCommit(false); } tx.commit(); } tx = null; err = false; } catch (SQLException e) { log.error("Error while closing the connection", e); } finally { if (err) { try { tx.rollback(); tx = null; } catch (Throwable t) { // ignore these exception not to hide others } } try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (SQLException e) { log.error("Error while closing the connection", e); } deleteSessionHandler(); closeSession(); } setSession(null); log.debug("Transaction closed, session closed"); } /** * Commits the transaction and starts a new transaction. */ public void commitAndStart() { Check.isFalse(TriggerHandler.getInstance().isDisabled(), "Triggers disabled, commit is not allowed when in triggers-disabled mode, " + "call TriggerHandler.enable() before committing"); checkInvariant(); flushRemainingChanges(); tx.commit(); tx = null; tx = getSession().beginTransaction(); log.debug("Committed and started new transaction"); } private void flushRemainingChanges() { // business event handlers can change the data // during flush, flush several times until // the session is really cleaned up int countFlushes = 0; while (OBDal.getInstance().getSession().isDirty()) { OBDal.getInstance().flush(); countFlushes++; // arbitrary point to give up... if (countFlushes > 100) { log.error("Infinite loop in flushing session, tried more than 100 flushes"); break; } } } /** * Rolls back the transaction and closes the getSession(). */ public void rollback() { log.debug("Rolling back transaction"); try { checkInvariant(); if (connection == null || (connection != null && !connection.isClosed())) { tx.rollback(); } tx = null; } catch (SQLException e) { log.error("Error while closing the connection", e); } finally { deleteSessionHandler(); try { if (connection != null && !connection.isClosed()) { connection.close(); } log.debug("Closing session"); closeSession(); } catch (SQLException e) { log.error("Error while closing the connection", e); } } setSession(null); } /** * The invariant is that for begin, rollback and commit the session etc. are alive */ private void checkInvariant() { Check.isNotNull(getSession(), "Session is null"); Check.isNotNull(tx, "Tx is null"); Check.isTrue(tx.isActive(), "Tx is not active"); } /** * Registers that the transaction should be rolled back. Is used by the {@link DalThreadHandler}. * * @param setRollback * if true then the transaction will be rolled back at the end of the thread. */ public void setDoRollback(boolean setRollback) { if (setRollback) { log.debug("Rollback is set to true"); } this.doRollback = setRollback; } /** @return the doRollback value */ public boolean getDoRollback() { return doRollback; } /** * Returns true if the session-in-view pattern should be supported. That is that the session is * closed and committed at the end of the request. * * @return always true in this implementation */ public boolean doSessionInViewPatter() { return true; } }