/*
Copyright 2012 Christian Prause and Fraunhofer FIT
Licensed 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.
*/
package prause.toolbox.hibernate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.persister.collection.AbstractCollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.util.Map;
public class HibernateUtil {
private static final Log logger = LogFactory.getLog(HibernateUtil.class);
private static SessionFactory sessionFactory;
private static int openCount = 0;
public static final ThreadLocal<SessionData> session = new ThreadLocal<SessionData>();
private static org.w3c.dom.Document hibernateParams;
public static synchronized void setSessionFactory(SessionFactory newFactory) {
SessionFactory oldFactory = sessionFactory;
if (newFactory == oldFactory) {
logger.warn("Not setting new session factory, because it is already set");
return;
}
sessionFactory = newFactory;
if (oldFactory != null) {
try {
// close old factory
oldFactory.close();
} catch (HibernateException e) {
logger.error("Failed to close old SessionFactory when switching to new one", e);
}
}
}
private static String toXML(org.w3c.dom.Document document) {
try {
// transform the Document into a String
DOMSource domSource = new DOMSource(document);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
//transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
java.io.StringWriter sw = new java.io.StringWriter();
StreamResult sr = new StreamResult(sw);
transformer.transform(domSource, sr);
return sw.toString();
} catch (Exception e) {
logger.error("Failed to transform configuration to XML", e);
return null;
}
}
public static synchronized void setHibernateParams(org.w3c.dom.Document hibernateParams) {
logger.info("Overriding Hibernate configuration parameters...");
HibernateUtil.hibernateParams = hibernateParams;
}
public static synchronized SessionFactory getSessionFactory() {
if (sessionFactory == null) {
try {
logger.info("Configuring Hibernate...");
//org.w3c.dom.Document hibernateParams = Configurator.get().getSubtree("hibernate");
if (hibernateParams != null) {
logger.debug("Using hibernate configuration from Configurator:\n" + toXML(hibernateParams));
setSessionFactory(new org.hibernate.cfg.Configuration().configure(hibernateParams).buildSessionFactory());
} else {
logger.info("Trying default hibernate configuration mechanism");
// Create the SessionFactory from hibernate.cfg.xml
setSessionFactory(new Configuration().configure().buildSessionFactory());
}
logger.info("Hibernate running.");
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
logger.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);
}
}
return sessionFactory;
}
public static Session getCurrentSession() {
return getCurrentSessionData().session;
}
private static synchronized SessionData getCurrentSessionData() {
SessionData sd = session.get();
// Open a new Session, if this Thread has none yet
if (sd == null) {
sd = new SessionData();
sd.session = getSessionFactory().openSession();
sd.session.getTransaction().begin();
session.set(sd);
openCount++;
if (logger.isDebugEnabled()) {
logger.debug("Hibernate session created (" + openCount + " open)");
}
}
return sd;
}
/**
* Closes the Hibernate session assigned to the executing thread.
* If the session is returned to be isDirty(), then the session will be flushed and committed before closing.
* <p/>
* Note: A session does not get dirty if you only save an object, but do not modify the object. This means,
* that the object does not get saved! If you use the HibernateUtil.save() method the objects will get saved.
*
* @see prause.toolbox.hibernate.HibernateUtil#save(Object)
*/
public static synchronized void closeSession() {
SessionData sd = session.get();
if (sd == null) {
return;
}
boolean isDirty = false;
try {
// isDirty() may cause an Exception if the session is broken and may not be flushed anymore
isDirty = sd.session.isDirty();
// isDirty() somehow does not work?! I do not know why...
// I had to completely disable the dirty stuff, because getting a changed object again resets the dirty flag :-(
//if (sd.forceFlush || sd.session.isDirty()) {
// if (logger.isDebugEnabled()) {
// logger.debug("Auto-flushing dirty session (" + sd.forceFlush + "," + sd.session.isDirty() + ")");
// }
sd.session.flush();
sd.session.getTransaction().commit();
//}
} catch (Exception e) {
// the exception will be reported by Hibernate, so we do not need to log it again
logger.error("Exception encountered when closing session, trying to roll back transaction...");
try {
sd.session.getTransaction().rollback();
logger.debug("Transaction rolled back");
} catch (Exception e2) {
logger.error("Rolling back failed!", e2);
}
} finally {
// flushing a session could call some persistent object's methods which in turn could call getCurrentSession(), so we release the thread-session-association as late as possible
session.set(null);
openCount--;
try {
sd.session.close();
if (logger.isDebugEnabled()) {
logger.debug("Hibernate (" + (isDirty ? "dirty" : "no changes to commit") + ") session released (" + openCount + " open)");
}
} catch (Exception e) {
logger.warn("Failed to close after roll back", e);
}
}
}
public static synchronized void abortSession() {
SessionData sd = session.get();
if (sd != null) {
logger.debug("Aborting session; transaction will be rolled back...");
session.set(null);
openCount--;
sd.session.getTransaction().rollback();
sd.session.close();
logger.debug("Session rolled back and closed");
}
}
public static synchronized void commit() {
getCurrentSession().flush();
getCurrentSession().getTransaction().commit();
getCurrentSession().getTransaction().begin();
}
/**
* Persist the specified object with the current session and make sure that the transaction will be committed
* even if no object is modified but only added.
*
* @param obj the object to be persisted
* @see HibernateUtil#closeSession()
*/
public static synchronized void save(Object obj) {
if (logger.isDebugEnabled()) {
logger.debug("Persisting: " + obj);
}
SessionData sessionData = getCurrentSessionData();
sessionData.session.save(obj);
sessionData.forceFlush = true;
sessionData.newObjectsInTransaction++;
/*if (sessionData.newObjectsInTransaction > 150) {
// this is a workaround for strange hibernate behaviour: When lots of new entities are created it becomes awfully slow
logger.warn("WORKAROUND: More than 150 new objects created, closing session");
closeSession();
}*/
}
public static void setDirty() {
SessionData sd = session.get();
if (sd != null) {
sd.forceFlush = true;
}
}
public static synchronized void clearDatabase() {
closeSession();
clearHibernateCache();
sessionFactory = null;
try {
new SchemaExport(new org.hibernate.cfg.Configuration().configure(hibernateParams)).create(false, true);
} catch (Exception e) {
logger.error("Failed to clear database because of an exception", e);
}
}
/**
* Clear Hibernate second level caches.
* Based on code taken from here: http://java.dzone.com/tips/clearing-hibernate-second-leve
*/
private static void clearHibernateCache() {
SessionFactory sf = getSessionFactory();
Map classMetadata = sf.getAllClassMetadata();
for (Object epo : classMetadata.values()) {
EntityPersister ep = (EntityPersister) epo;
if (ep.hasCache()) {
sf.evictEntity(ep.getCache().getRegionName());
}
}
Map collMetadata = sf.getAllCollectionMetadata();
for (Object abstractCollectionPersistObject : collMetadata.values()) {
AbstractCollectionPersister acp = (AbstractCollectionPersister) abstractCollectionPersistObject;
if (acp.hasCache()) {
sf.evictCollection(acp.getCache().getRegionName());
}
}
}
private static class SessionData {
private Session session;
private boolean forceFlush = false;
private int newObjectsInTransaction = 0;
}
}