001    /*******************************************************************************
002     * Copyright (c) PicoContainer Organization. All rights reserved. 
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * license.html file.
007     ******************************************************************************/
008    
009    package org.picocontainer.persistence.hibernate;
010    
011    import java.sql.Connection;
012    
013    import org.hibernate.HibernateException;
014    import org.hibernate.Interceptor;
015    import org.hibernate.Session;
016    import org.hibernate.SessionFactory;
017    import org.picocontainer.Disposable;
018    import org.picocontainer.Startable;
019    
020    /**
021     * <p>
022     * Session implementation which allows request scoping while supporting
023     * lifecycle events. If you register this component within either a request
024     * PicoContainer or a request-scoped storage object, then this component will
025     * close sessions whenever stop() is called by the container, and is reusable
026     * until dispose() is called.
027     * </p>
028     * <p>
029     * This allows for the &quot;One Session Per Request&quot; pattern often used in
030     * Hibernate.
031     * <p>
032     * 
033     * @author Jose Peleteiro
034     * @author Michael Rimov
035     * @author Mauro Talevi
036     */
037    @SuppressWarnings("serial")
038    public final class ScopedSession extends AbstractSessionDecorator implements Startable, Disposable {
039    
040        /**
041         * Session factory that creates the delegate sessions.
042         */
043        private final SessionFactory factory;
044    
045        /**
046         * Current delegate session, lazily created.
047         */
048        private Session session = null;
049    
050        /**
051         * Session-specific interceptor.
052         */
053        private Interceptor interceptor = null;
054    
055        /**
056         * Flag indicating object should not be used again.
057         */
058        private boolean disposed = false;
059    
060        /**
061         * Creates a ScopedSession with factory and <code>null</code> interceptor
062         * 
063         * @param factory session factory to create the session
064         */
065        public ScopedSession(final SessionFactory factory) {
066            this.factory = factory;
067        }
068    
069        /**
070         * Creates a ScopedSession with factory and interceptor
071         * 
072         * @param factory session factory to create the session
073         * @param interceptor interceptor to use with created session
074         */
075        public ScopedSession(final SessionFactory factory, final Interceptor interceptor) {
076            this(factory);
077            setInterceptor(interceptor);
078        }
079    
080        /** {@inheritDoc} */
081        @Override
082        public SessionFactory getSessionFactory() {
083            return factory;
084        }
085    
086        /**
087         * Obtain hibernate session in lazy way.
088         */
089        @Override
090        public Session getDelegate() {
091            if (disposed) {
092                throw new IllegalStateException("Component has already been disposed by parent container.");
093            }
094    
095            if (session == null) {
096                try {
097                    session = interceptor == null ? factory.openSession() : factory.openSession(interceptor);
098                } catch (RuntimeException ex) {
099                    throw handleException(ex);
100                }
101            }
102    
103            return session;
104        }
105    
106        /**
107         * {@inheritDoc}
108         * <p>
109         * Because this implementation decorates a delegate session, it removes the
110         * delegate session, but it does allow re-referencing once close() has been
111         * called. It simply grabs a new Hibernate session.
112         * </p>
113         */
114        @Override
115        public Connection close() {
116            try {
117                return getDelegate().close();
118            } catch (HibernateException ex) {
119                session = null;
120                throw handleException(ex);
121            } finally {
122                session = null;
123            }
124        }
125    
126        @Override
127        public void invalidateDelegate() {
128            if (session != null) {
129                try {
130                    session.clear();
131                    session.close();
132                } catch (HibernateException ex) {
133                    session = null;
134                    throw handleException(ex);
135                } finally {
136                    session = null;
137                }
138            }
139        }
140    
141        /**
142         * Returns the current interceptor.
143         * 
144         * @return The Interceptor
145         */
146        public Interceptor getInterceptor() {
147            return interceptor;
148        }
149    
150        /**
151         * Sets a new hibernate session interceptor. This is only applicable if
152         * there is no current session. If this session object has been used, then
153         * please call close() first.
154         * 
155         * @param interceptor the Interceptor to apply to this session.
156         * @throws IllegalStateException if this session has already been utilized
157         *             after creation.
158         */
159        public void setInterceptor(final Interceptor interceptor) throws IllegalStateException {
160            if (session != null) {
161                throw new IllegalStateException("Cannot apply interceptor after session has been utilized");
162            }
163    
164            this.interceptor = interceptor;
165        }
166    
167        /**
168         * Add some insurance against potential memory leaks. Make sure that Session
169         * is closed.
170         * <p>
171         * {@inheritDoc}
172         * </p>
173         */
174        @Override
175        protected void finalize() throws Throwable {
176            try {
177                if (session != null) {
178                    session.close();
179                }
180            } finally {
181                super.finalize();
182            }
183        }
184    
185        /** {@inheritDoc} * */
186        @Override
187        public int hashCode() {
188            if (session == null) {
189                return 13;
190            }
191    
192            return session.hashCode();
193    
194        }
195    
196        /** {@inheritDoc} * */
197        @Override
198        public String toString() {
199            return ScopedSession.class.getName() + " using current Session : " + session;
200        }
201    
202        /**
203         * {@inheritDoc}
204         * <p>
205         * Currently does nothing. Session is lazily created .
206         * </p>
207         */
208        public void start() {
209            // currently does nothing. Session is lazily created
210        }
211    
212        /**
213         * {@inheritDoc}
214         * <p>
215         * Closes and invalidates any sessions that are still open.
216         * </p>
217         */
218        public void stop() {
219            this.close();
220    
221        }
222    
223        /**
224         * {@inheritDoc}
225         * <p>
226         * Prevents any further utilization once called.
227         * </p>
228         */
229        public void dispose() {
230            if (this.session != null) {
231                close();
232            }
233    
234            disposed = true;
235        }
236    
237    }