001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.hibernate3;
018:
019: import java.lang.reflect.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.hibernate.HibernateException;
027: import org.hibernate.JDBCException;
028: import org.hibernate.SessionFactory;
029: import org.hibernate.engine.SessionFactoryImplementor;
030:
031: import org.springframework.beans.factory.DisposableBean;
032: import org.springframework.beans.factory.FactoryBean;
033: import org.springframework.beans.factory.InitializingBean;
034: import org.springframework.dao.DataAccessException;
035: import org.springframework.dao.support.PersistenceExceptionTranslator;
036: import org.springframework.jdbc.support.SQLExceptionTranslator;
037:
038: /**
039: * Abstract {@link org.springframework.beans.factory.FactoryBean} that creates
040: * a Hibernate {@link org.hibernate.SessionFactory} within a Spring application
041: * context. Supports building a transaction-aware SessionFactory proxy that
042: * exposes a Spring-managed transactional Session as "current Session".
043: *
044: * <p>This class also implements the
045: * {@link org.springframework.dao.support.PersistenceExceptionTranslator}
046: * interface, as autodetected by Spring's
047: * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor},
048: * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
049: * Hence, the presence of e.g. LocalSessionFactoryBean automatically enables
050: * a PersistenceExceptionTranslationPostProcessor to translate Hibernate exceptions.
051: *
052: * <p>This class mainly serves as common base class for {@link LocalSessionFactoryBean}.
053: * For details on typical SessionFactory setup, see the LocalSessionFactoryBean javadoc.
054: *
055: * @author Juergen Hoeller
056: * @since 2.0
057: * @see #setExposeTransactionAwareSessionFactory
058: * @see org.hibernate.SessionFactory#getCurrentSession()
059: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
060: */
061: public abstract class AbstractSessionFactoryBean implements
062: FactoryBean, InitializingBean, DisposableBean,
063: PersistenceExceptionTranslator {
064:
065: /** Logger available to subclasses */
066: protected final Log logger = LogFactory.getLog(getClass());
067:
068: private boolean exposeTransactionAwareSessionFactory = true;
069:
070: private SQLExceptionTranslator jdbcExceptionTranslator;
071:
072: private SessionFactory sessionFactory;
073:
074: /**
075: * Set whether to expose a transaction-aware proxy for the SessionFactory,
076: * returning the Session that's associated with the current Spring-managed
077: * transaction on <code>getCurrentSession()</code>, if any.
078: * <p>Default is "true", letting data access code work with the plain
079: * Hibernate SessionFactory and its <code>getCurrentSession()</code> method,
080: * while still being able to participate in current Spring-managed transactions:
081: * with any transaction management strategy, either local or JTA / EJB CMT,
082: * and any transaction synchronization mechanism, either Spring or JTA.
083: * Furthermore, <code>getCurrentSession()</code> will also seamlessly work with
084: * a request-scoped Session managed by OpenSessionInViewFilter/Interceptor.
085: * <p>Turn this flag off to expose the plain Hibernate SessionFactory with
086: * Hibernate's default <code>getCurrentSession()</code> behavior, where
087: * Hibernate 3.0.x only supports plain JTA synchronization. On Hibernate 3.1+,
088: * such a plain SessionFactory will by default have a SpringSessionContext
089: * registered to nevertheless provide Spring-managed Sessions; this can be
090: * overridden through the corresponding Hibernate property
091: * "hibernate.current_session_context_class".
092: * @see org.hibernate.SessionFactory#getCurrentSession()
093: * @see org.springframework.transaction.jta.JtaTransactionManager
094: * @see HibernateTransactionManager
095: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
096: * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
097: * @see SpringSessionContext
098: */
099: public void setExposeTransactionAwareSessionFactory(
100: boolean exposeTransactionAwareSessionFactory) {
101: this .exposeTransactionAwareSessionFactory = exposeTransactionAwareSessionFactory;
102: }
103:
104: /**
105: * Return whether to expose a transaction-aware proxy for the SessionFactory.
106: */
107: protected boolean isExposeTransactionAwareSessionFactory() {
108: return exposeTransactionAwareSessionFactory;
109: }
110:
111: /**
112: * Set the JDBC exception translator for the SessionFactory,
113: * exposed via the PersistenceExceptionTranslator interface.
114: * <p>Applied to any SQLException root cause of a Hibernate JDBCException,
115: * overriding Hibernate's default SQLException translation (which is
116: * based on Hibernate's Dialect for a specific target database).
117: * @param jdbcExceptionTranslator the exception translator
118: * @see java.sql.SQLException
119: * @see org.hibernate.JDBCException
120: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
121: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
122: * @see org.springframework.dao.support.PersistenceExceptionTranslator
123: */
124: public void setJdbcExceptionTranslator(
125: SQLExceptionTranslator jdbcExceptionTranslator) {
126: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
127: }
128:
129: /**
130: * Build and expose the SessionFactory.
131: * @see #buildSessionFactory()
132: * @see #wrapSessionFactoryIfNecessary
133: */
134: public void afterPropertiesSet() throws Exception {
135: SessionFactory rawSf = buildSessionFactory();
136: this .sessionFactory = wrapSessionFactoryIfNecessary(rawSf);
137: afterSessionFactoryCreation();
138: }
139:
140: /**
141: * Wrap the given SessionFactory with a transaction-aware proxy, if demanded.
142: * @param rawSf the raw SessionFactory as built by <code>buildSessionFactory()</code>
143: * @return the SessionFactory reference to expose
144: * @see #buildSessionFactory()
145: * @see #getTransactionAwareSessionFactoryProxy
146: */
147: protected SessionFactory wrapSessionFactoryIfNecessary(
148: SessionFactory rawSf) {
149: if (isExposeTransactionAwareSessionFactory()) {
150: return getTransactionAwareSessionFactoryProxy(rawSf);
151: } else {
152: return rawSf;
153: }
154: }
155:
156: /**
157: * Wrap the given SessionFactory with a proxy that delegates every method call
158: * to it but delegates <code>getCurrentSession</code> calls to SessionFactoryUtils,
159: * for participating in Spring-managed transactions.
160: * @param target the original SessionFactory to wrap
161: * @return the wrapped SessionFactory
162: * @see org.hibernate.SessionFactory#getCurrentSession()
163: * @see SessionFactoryUtils#doGetSession(org.hibernate.SessionFactory, boolean)
164: */
165: protected SessionFactory getTransactionAwareSessionFactoryProxy(
166: SessionFactory target) {
167: Class sfInterface = SessionFactory.class;
168: if (target instanceof SessionFactoryImplementor) {
169: sfInterface = SessionFactoryImplementor.class;
170: }
171: return (SessionFactory) Proxy.newProxyInstance(sfInterface
172: .getClassLoader(), new Class[] { sfInterface },
173: new TransactionAwareInvocationHandler(target));
174: }
175:
176: /**
177: * Return the exposed SessionFactory.
178: * @throws IllegalStateException if the SessionFactory has not been initialized yet
179: */
180: protected final SessionFactory getSessionFactory() {
181: if (this .sessionFactory == null) {
182: throw new IllegalStateException(
183: "SessionFactory not initialized yet");
184: }
185: return this .sessionFactory;
186: }
187:
188: /**
189: * Close the SessionFactory on bean factory shutdown.
190: */
191: public void destroy() throws HibernateException {
192: logger.info("Closing Hibernate SessionFactory");
193: try {
194: beforeSessionFactoryDestruction();
195: } finally {
196: this .sessionFactory.close();
197: }
198: }
199:
200: /**
201: * Return the singleton SessionFactory.
202: */
203: public Object getObject() {
204: return this .sessionFactory;
205: }
206:
207: public Class getObjectType() {
208: return (this .sessionFactory != null) ? this .sessionFactory
209: .getClass() : SessionFactory.class;
210: }
211:
212: public boolean isSingleton() {
213: return true;
214: }
215:
216: /**
217: * Implementation of the PersistenceExceptionTranslator interface,
218: * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
219: * <p>Converts the exception if it is a HibernateException;
220: * else returns <code>null</code> to indicate an unknown exception.
221: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
222: * @see #convertHibernateAccessException
223: */
224: public DataAccessException translateExceptionIfPossible(
225: RuntimeException ex) {
226: if (ex instanceof HibernateException) {
227: return convertHibernateAccessException((HibernateException) ex);
228: }
229: return null;
230: }
231:
232: /**
233: * Convert the given HibernateException to an appropriate exception from the
234: * <code>org.springframework.dao</code> hierarchy.
235: * <p>Will automatically apply a specified SQLExceptionTranslator to a
236: * Hibernate JDBCException, else rely on Hibernate's default translation.
237: * @param ex HibernateException that occured
238: * @return a corresponding DataAccessException
239: * @see SessionFactoryUtils#convertHibernateAccessException
240: * @see #setJdbcExceptionTranslator
241: */
242: protected DataAccessException convertHibernateAccessException(
243: HibernateException ex) {
244: if (this .jdbcExceptionTranslator != null
245: && ex instanceof JDBCException) {
246: JDBCException jdbcEx = (JDBCException) ex;
247: return this .jdbcExceptionTranslator.translate(
248: "Hibernate operation: " + jdbcEx.getMessage(),
249: jdbcEx.getSQL(), jdbcEx.getSQLException());
250: }
251: return SessionFactoryUtils.convertHibernateAccessException(ex);
252: }
253:
254: /**
255: * Build the underlying Hibernate SessionFactory.
256: * @return the raw SessionFactory (potentially to be wrapped with a
257: * transaction-aware proxy before it is exposed to the application)
258: * @throws Exception in case of initialization failure
259: */
260: protected abstract SessionFactory buildSessionFactory()
261: throws Exception;
262:
263: /**
264: * Hook that allows post-processing after the SessionFactory has been
265: * successfully created. The SessionFactory is already available through
266: * <code>getSessionFactory()</code> at this point.
267: * <p>This implementation is empty.
268: * @see #getSessionFactory()
269: */
270: protected void afterSessionFactoryCreation() throws Exception {
271: }
272:
273: /**
274: * Hook that allows shutdown processing before the SessionFactory
275: * will be closed. The SessionFactory is still available through
276: * <code>getSessionFactory()</code> at this point.
277: * <p>This implementation is empty.
278: * @see #getSessionFactory()
279: */
280: protected void beforeSessionFactoryDestruction() {
281: }
282:
283: /**
284: * Invocation handler that delegates <code>getCurrentSession()</code> calls
285: * to SessionFactoryUtils, for being aware of thread-bound transactions.
286: */
287: private static class TransactionAwareInvocationHandler implements
288: InvocationHandler {
289:
290: private final SessionFactory target;
291:
292: public TransactionAwareInvocationHandler(SessionFactory target) {
293: this .target = target;
294: }
295:
296: public Object invoke(Object proxy, Method method, Object[] args)
297: throws Throwable {
298: // Invocation on SessionFactory/SessionFactoryImplementor interface coming in...
299:
300: if (method.getName().equals("getCurrentSession")) {
301: // Handle getCurrentSession method: return transactional Session, if any.
302: try {
303: return SessionFactoryUtils.doGetSession(
304: (SessionFactory) proxy, false);
305: } catch (IllegalStateException ex) {
306: throw new HibernateException(ex.getMessage());
307: }
308: } else if (method.getName().equals("equals")) {
309: // Only consider equal when proxies are identical.
310: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
311: } else if (method.getName().equals("hashCode")) {
312: // Use hashCode of SessionFactory proxy.
313: return new Integer(hashCode());
314: }
315:
316: // Invoke method on target SessionFactory.
317: try {
318: return method.invoke(this .target, args);
319: } catch (InvocationTargetException ex) {
320: throw ex.getTargetException();
321: }
322: }
323: }
324:
325: }
|