View Javadoc

1   package com.melloware.jukes.db;
2   
3   import java.sql.SQLException;
4   import java.sql.Statement;
5   import java.util.Enumeration;
6   import java.util.HashMap;
7   import java.util.Map;
8   import java.util.Properties;
9   
10  import org.apache.commons.lang.StringUtils;
11  import org.apache.commons.logging.Log;
12  import org.apache.commons.logging.LogFactory;
13  import org.hibernate.HibernateException;
14  import org.hibernate.Interceptor;
15  import org.hibernate.Session;
16  import org.hibernate.SessionFactory;
17  import org.hibernate.Transaction;
18  import org.hibernate.cfg.Configuration;
19  import org.hibernate.cfg.Environment;
20  import org.hibernate.stat.Statistics;
21  
22  import com.melloware.jukes.exception.InfrastructureException;
23  
24  /**
25   * Basic Hibernate helper class for Hibernate configuration and startup.
26   * <p>
27   * Uses a static initializer to read startup options and initialize
28   * <tt>Configuration</tt> and <tt>SessionFactory</tt>.
29   * <p>
30   * This class also tries to figure out if JNDI binding of the
31   * <tt>SessionFactory</tt> is used, otherwise it falls back to a global static
32   * variable (Singleton). If you use this helper class to obtain a
33   * <tt>SessionFactory</tt> in your code, you are shielded from these
34   * deployment differences.
35   * <p>
36   * Another advantage of this class is access to the <tt>Configuration</tt>
37   * object that was used to build the current <tt>SessionFactory</tt>. You can
38   * access mapping metadata programmatically with this API, and even change it
39   * and rebuild the <tt>SessionFactory</tt>.
40   * <p>
41   * If you want to assign a global interceptor, set its fully qualified class
42   * name with the system (or hibernate.properties/hibernate.cfg.xml) property
43   * <tt>hibernate.util.interceptor_class</tt>. It will be loaded and
44   * instantiated on static initialization of HibernateUtil; it has to have a
45   * no-argument constructor. You can call <tt>HibernateUtil.getInterceptor()</tt>
46   * if you need to provide settings before using the interceptor.
47   * <p>
48   * Note: This class supports annotations by default, hence needs JDK 5.0 and the
49   * Hibernate Annotations library on the classpath. Change the single commented
50   * line in the source to make it compile and run on older JDKs with XML mapping
51   * files only.
52   * <p>
53   * Note: This class supports only one data store. Support for several
54   * <tt>SessionFactory</tt> instances can be easily added (through a static
55   * <tt>Map</tt>, for example). You could then lookup a
56   * <tt>SessionFactory</tt> by its name. Copyright (c) 1999-2007 Melloware,
57   * Inc. <http://www.melloware.com>
58   * @author Emil A. Lefkof III <info@melloware.com>
59   * @author christian@hibernate.org
60   */
61  @SuppressWarnings("unchecked")
62  public final class HibernateUtil {
63  
64     private static final Log LOG = LogFactory.getLog(HibernateUtil.class);
65  
66     private static final String HIBERNATE = "hibernate";
67     private static final String INTERCEPTOR_CLASS = "hibernate.util.interceptor_class";
68     private static final Map threadSession = new HashMap();
69     private static final Map threadTransaction = new HashMap();
70     private static final Map threadInterceptor = new HashMap();
71     private static Configuration configuration;
72     private static SessionFactory sessionFactory;
73     private static String remoteUrl = null;
74     private static boolean isHSQLDialect = false;
75  
76     /**
77      * This flag is set to true to compact the database. This is slow and only
78      * should be done after a bulk load or update like Disc Finder
79      */
80     private static boolean compact = false;
81  
82     /**
83      * Default constructor. Private so no instantiation.
84      */
85     private HibernateUtil() {
86        super();
87     }
88  
89     /**
90      * Returns the original Hibernate configuration.
91      * @return Configuration
92      */
93     public static Configuration getConfiguration() {
94        return configuration;
95     }
96  
97     /**
98      * Gets the remoteUrl.
99      * <p>
100     * @return Returns the remoteUrl.
101     */
102    public static String getRemoteUrl() {
103       return remoteUrl;
104    }
105 
106    /**
107     * Retrieves the current Session local to the thread. <p/> If no Session is
108     * open, opens a new Session for the running thread.
109     * @return Session
110     */
111    @SuppressWarnings("unchecked")
112    public static Session getSession() throws InfrastructureException {
113       Session s = (Session) threadSession.get(HIBERNATE);
114       try {
115          if (s == null) {
116             LOG.debug("Opening new Session for this thread." + Thread.currentThread().getName());
117             if (getInterceptor() == null) {
118                s = getSessionFactory().openSession();
119             } else {
120                LOG.debug("Using interceptor: " + getInterceptor().getClass());
121                s = getSessionFactory().openSession(getInterceptor());
122 
123             }
124             threadSession.put(HIBERNATE, s);
125          }
126       } catch (HibernateException ex) {
127          throw new InfrastructureException(ex);
128       }
129       return s;
130    }
131 
132    /**
133     * Returns the SessionFactory used for this static class.
134     * @return SessionFactory
135     */
136    public static SessionFactory getSessionFactory() {
137 
138       /*
139        * Instead of a static variable, use JNDI: SessionFactory sessions = null;
140        * try { Context ctx = new InitialContext(); String jndiName =
141        * "java:hibernate/HibernateFactory"; sessions =
142        * (SessionFactory)ctx.lookup(jndiName); } catch (NamingException ex) {
143        * throw new InfrastructureException(ex); } return sessions;
144        */
145       return sessionFactory;
146    }
147 
148    /**
149     * Sets the compact.
150     * <p>
151     * @param aCompact The compact to set.
152     */
153    public static void setCompact(final boolean aCompact) {
154       compact = aCompact;
155    }
156 
157    /**
158     * Sets the remoteUrl.
159     * <p>
160     * @param aRemoteUrl The remoteUrl to set.
161     */
162    public static void setRemoteUrl(final String aRemoteUrl) {
163       remoteUrl = aRemoteUrl;
164    }
165 
166    /**
167     * Gets the compact.
168     * <p>
169     * @return Returns the compact.
170     */
171    public static boolean isCompact() {
172       return compact;
173    }
174 
175    /**
176     * Gets the isHSQLDialect.
177     * <p>
178     * @return Returns the isHSQLDialect.
179     */
180    public static boolean isHSQLDialect() {
181       return isHSQLDialect;
182    }
183 
184    /**
185     * Start a new database transaction.
186     */
187    @SuppressWarnings("unchecked")
188    public static void beginTransaction() throws InfrastructureException {
189       Transaction tx = (Transaction) threadTransaction.get(HIBERNATE);
190       try {
191          if (tx == null) {
192             LOG.debug("Starting new database transaction in this thread.");
193             tx = getSession().beginTransaction();
194             threadTransaction.put(HIBERNATE, tx);
195          }
196       } catch (HibernateException ex) {
197          throw new InfrastructureException(ex);
198       }
199    }
200 
201    /**
202     * Closes the Session local to the thread.
203     */
204    @SuppressWarnings("unchecked")
205    public static void closeSession() throws InfrastructureException {
206       try {
207          final Session s = (Session) threadSession.get(HIBERNATE);
208          threadSession.put(HIBERNATE, null);
209          if ((s != null) && s.isOpen()) {
210             LOG.debug("Closing Session of this thread.");
211             s.close();
212          }
213       } catch (HibernateException ex) {
214          throw new InfrastructureException(ex);
215       }
216    }
217 
218    /**
219     * Commit the database transaction.
220     */
221    @SuppressWarnings("unchecked")
222    public static void commitTransaction() throws InfrastructureException {
223       final Transaction tx = (Transaction) threadTransaction.get(HIBERNATE);
224       try {
225          if ((tx != null) && !tx.wasCommitted() && !tx.wasRolledBack()) {
226             LOG.debug("Committing database transaction of this thread.");
227             tx.commit();
228          }
229          threadTransaction.put(HIBERNATE, null);
230       } catch (HibernateException ex) {
231          rollbackTransaction();
232          throw new InfrastructureException(ex);
233       }
234    }
235 
236    /**
237     * Disconnect and return Session from current Thread.
238     * @return Session the disconnected Session
239     */
240    public static Session disconnectSession() throws InfrastructureException {
241 
242       final Session session = getSession();
243       try {
244          threadSession.put(HIBERNATE, null);
245          if (session.isConnected() && session.isOpen()) {
246             session.disconnect();
247          }
248       } catch (HibernateException ex) {
249          throw new InfrastructureException(ex);
250       }
251       return session;
252    }
253 
254    /**
255     * Static initializer to startup Hibernate.
256     */
257    public static void initialize() {
258       if (configuration != null) {
259          return;
260       }
261 
262       // Create the initial SessionFactory from the default configuration files
263       try {
264          LOG.info("Hibernate Initialization");
265          // Replace with Configuration() if you don't use annotations or JDK 5.0
266          // configuration = new
267          // AnnotationConfiguration();
268 
269          // This custom entity resolver supports entity placeholders in XML
270          // mapping files
271          // and tries to resolve them on the classpath as a resource
272          // configuration.setEntityResolver(new
273          // ImportFromClasspathEntityResolver());
274 
275          // Read not only hibernate.properties, but also hibernate.cfg.xml
276 
277          configuration = new Configuration();
278 
279          configuration.configure();
280          final Properties cfg = configuration.getProperties();
281 
282          // use the remoteURL if it is set
283          if (remoteUrl == null) {
284             configuration.getProperties().put("hibernate.hbm2ddl.auto", "update");
285             configuration.setProperty("hibernate.hbm2ddl.auto", "update");
286          } else {
287             LOG.info("Remote URL Connection: " + remoteUrl);
288             configuration.setProperty("hibernate.connection.url", remoteUrl);
289             configuration.getProperties().remove("hbm2ddl.auto");
290             configuration.getProperties().remove("hibernate.hbm2ddl.auto");
291          }
292 
293          final Enumeration names = cfg.propertyNames();
294          // add all hibernate props to system props
295          while (names.hasMoreElements()) {
296             final String name = (String) names.nextElement();
297             final String value = cfg.getProperty(name);
298             System.getProperties().put(name, value);
299          }
300          System.getProperties().put("hibernate.version", Environment.VERSION);
301 
302          // Assign a global, user-defined interceptor with no-arg constructor
303          final String interceptorName = configuration.getProperty(INTERCEPTOR_CLASS);
304          if (interceptorName != null) {
305             LOG.info("Initializing Interceptor: " + interceptorName);
306             final Class interceptorClass = HibernateUtil.class.getClassLoader().loadClass(interceptorName);
307             final Interceptor interceptor = (Interceptor) interceptorClass.newInstance();
308             configuration.setInterceptor(interceptor);
309          }
310 
311          if (configuration.getProperty(Environment.SESSION_FACTORY_NAME) == null) {
312 
313             // or use static variable handling
314             sessionFactory = configuration.buildSessionFactory();
315          } else {
316             // Let Hibernate bind the factory to JNDI
317             configuration.buildSessionFactory();
318          }
319 
320          // enable statistics
321          final Statistics stats = sessionFactory.getStatistics();
322          stats.setStatisticsEnabled(true);
323          LOG.info("Hibernate " + Environment.VERSION);
324 
325          // Special handling for HSQLDB, which requires an explicit shutdown
326          // since version 1.7.x or so
327          final String dialectName = configuration.buildSettings().getDialect().toString();
328          isHSQLDialect = "org.hibernate.dialect.HSQLDialect".equals(dialectName);
329       } catch (Throwable ex) {
330          // We have to catch Throwable, otherwise we will miss
331          // NoClassDefFoundError and other subclasses of Error
332          LOG.error("Building SessionFactory failed.", ex);
333          throw new ExceptionInInitializerError(ex);
334       }
335    }
336 
337    /**
338     * Rebuild the SessionFactory with the static Configuration.
339     */
340    public static void rebuildSessionFactory() throws InfrastructureException {
341       synchronized (sessionFactory) {
342          LOG.info("Rebuilding session factory.");
343          try {
344             sessionFactory = getConfiguration().buildSessionFactory();
345          } catch (Exception ex) {
346             throw new InfrastructureException(ex);
347          }
348       }
349    }
350 
351    /**
352     * Rebuild the SessionFactory with the given Hibernate Configuration.
353     * @param cfg
354     */
355    public static void rebuildSessionFactory(final Configuration cfg) throws InfrastructureException {
356       synchronized (sessionFactory) {
357          try {
358             sessionFactory = cfg.buildSessionFactory();
359             configuration = cfg;
360          } catch (Exception ex) {
361             throw new InfrastructureException(ex);
362          }
363       }
364    }
365 
366    /**
367     * Register a Hibernate interceptor with the current thread.
368     * <p>
369     * Every Session opened is opened with this interceptor after registration.
370     * Has no effect if the current Session of the thread is already open,
371     * effective on next close()/getSession().
372     */
373    public static void registerInterceptor(final Interceptor interceptor) {
374       threadInterceptor.put(HIBERNATE, interceptor);
375    }
376 
377    /**
378     * Commit the database transaction.
379     */
380    public static void rollbackTransaction() throws InfrastructureException {
381       final Transaction tx = (Transaction) threadTransaction.get(HIBERNATE);
382       try {
383          threadTransaction.put(HIBERNATE, null);
384          if ((tx != null) && !tx.wasCommitted() && !tx.wasRolledBack()) {
385             LOG.debug("Trying to rollback database transaction of this thread.");
386             tx.rollback();
387          }
388       } catch (HibernateException ex) {
389          throw new InfrastructureException(ex);
390       } finally {
391          closeSession();
392       }
393    }
394 
395    /**
396     * Closes the current SessionFactory and releases all resources.
397     * <p>
398     * The only other method that can be called on HibernateUtil after this one
399     * is rebuildSessionFactory(Configuration).
400     */
401    @SuppressWarnings("deprecation")
402    public static void shutdown() {
403       LOG.info("Shutting down Hibernate.");
404 
405       // if sessionFactory is null someone shut us down already
406       if (sessionFactory == null) {
407          return;
408       }
409 
410       // special HSQLDB handling of proper shutdown, do not shut down remote
411       if ((isHSQLDialect) && (StringUtils.isBlank(getRemoteUrl()))) {
412          Statement stmt = null;
413          try {
414             String command = "SHUTDOWN";
415             if (isCompact()) {
416                command = "SHUTDOWN COMPACT";
417             }
418             final Session session = getSession();
419             LOG.info("HSQLDB: " + command);
420             stmt = session.connection().createStatement();
421             stmt.execute(command);
422          } catch (Throwable ex) {
423             LOG.error("Could not compact database", ex);
424          } finally {
425             if (stmt != null) {
426                try {
427                   stmt.close();
428                } catch (SQLException ex) {
429                   LOG.error("SQLException", ex);
430                   throw new InfrastructureException(ex);
431                }
432             }
433          }
434       }
435 
436       // clear any thread safe variables
437       threadInterceptor.put(HIBERNATE, null);
438       rollbackTransaction();
439 
440       // close the open sessions
441       closeSession();
442 
443       // Close caches and connection pools
444       getSessionFactory().close();
445 
446       // Clear static variables
447       configuration = null;
448       sessionFactory = null;
449    }
450 
451    /**
452     * Gets the inteceptor on the current thread.
453     * <p>
454     * @return the Interceptor if there is one in the current thread
455     */
456    private static Interceptor getInterceptor() {
457       return (Interceptor) threadInterceptor.get(HIBERNATE);
458    }
459 
460 }