001    /*
002     * Databinder: a simple bridge from Wicket to Hibernate
003     * Copyright (C) 2006  Nathan Hamblen nathan@technically.us
004     *
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     * 
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * Lesser General Public License for more details.
014     * 
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
018     */
019    
020    package net.databinder.hib;
021    
022    
023    
024    import org.apache.wicket.Application;
025    import org.apache.wicket.RequestCycle;
026    import org.apache.wicket.WicketRuntimeException;
027    import org.hibernate.SessionFactory;
028    import org.hibernate.context.ManagedSessionContext;
029    
030    /**
031     * Provides access to application-bound Hibernate session factories and current sessions.
032     * This class will work with a
033     * <a href="http://www.hibernate.org/hib_docs/v3/api/org/hibernate/context/ManagedSessionContext.html">ManagedSessionContext</a>
034     * and DataRequestCycle listener when present, but neither is required so long as a
035     * "current" session is available from the session factory supplied by the application.
036     * @see HibernateApplication
037     * @author Nathan Hamblen
038     */
039    public class Databinder {
040            
041            /**
042             * @return default session factory, as returned by the application
043             * @throws WicketRuntimeException if session factory can not be found 
044             * @see HibernateApplication
045             */
046            public static SessionFactory getHibernateSessionFactory() {
047                    return getHibernateSessionFactory(null);
048            }
049            /**
050             * @param key object, or null for the default factory
051             * @return session factory, as returned by the application
052             * @throws WicketRuntimeException if session factory can not be found 
053             * @see HibernateApplication
054             */
055            public static SessionFactory getHibernateSessionFactory(Object key) {
056                    Application app = Application.get();
057                    if (app instanceof HibernateApplication)
058                            return ((HibernateApplication)app).getHibernateSessionFactory(key);
059                    throw new WicketRuntimeException("Please implement HibernateApplication in your Application subclass.");
060            }
061            
062            /**
063             * @return default Hibernate session bound to current thread
064             */
065            public static org.hibernate.classic.Session getHibernateSession() {
066                    return getHibernateSession(null);
067            }
068            /**
069             * @param key or null for the default factory
070             * @return Hibernate session bound to current thread
071             */
072            public static org.hibernate.classic.Session getHibernateSession(Object key) {
073                    dataSessionRequested(key);
074                    return getHibernateSessionFactory(key).getCurrentSession();
075            }
076            /**
077             * @return true if a session is bound for the default factory
078             */
079            public static boolean hasBoundSession() {
080                    return hasBoundSession(null);
081            }
082            
083            /**
084             * @param key or null for the default factory
085             * @return true if a session is bound for the keyed factory
086             */
087            public static boolean hasBoundSession(Object key) {
088                    return ManagedSessionContext.hasBind(getHibernateSessionFactory(key));
089            }
090            
091            /**
092             * Notifies current request cycle that a data session was requested, if a session factory
093             * was not already bound for this thread and the request cycle is an DataRequestCycle.
094             * @param key or null for the default factory
095             * @see HibernateRequestCycle
096             */
097            private static void dataSessionRequested(Object key) {
098                    if (!hasBoundSession(key)) {
099                            // if session is unavailable, it could be a late-loaded conversational cycle
100                            RequestCycle cycle = RequestCycle.get();
101                            if (cycle instanceof HibernateRequestCycle)
102                                    ((HibernateRequestCycle)cycle).dataSessionRequested(key);
103                    }
104            }
105            
106            /**
107             * Wraps SessionUnit callback in a temporary thread-bound Hibernate session from the default
108             * factory if necessary. This is to be used outside of a regular a session-handling request cycle,
109             * such as during application init or an external Web service request. 
110             * The temporary session and transaction, if created, are closed after the callback returns and 
111             * uncommited transactions are rolled back. Be careful of returning detached Hibernate 
112             * objects that may not be fully loaded with data; consider using projections / scalar 
113             * queries instead.<b>Note</b> This method uses a ManagedSessionContext. With JTA
114             * or other forms of current session lookup a wrapping session will not be
115             * detected and a new one will always be created.
116             * @param unit work to be performed in thread-bound session
117             * @see SessionUnit
118             */
119            public static Object ensureSession(SessionUnit unit) {
120                    return ensureSession(unit, null);
121            }
122            /**
123             * Wraps SessionUnit callback in a temporary thread-bound Hibernate session from the keyed
124             * factory if necessary. This is to be used outside of a regular a session-handling request cycle,
125             * such as during application init or an external Web service request. 
126             * The temporary session and transaction, if created, are closed after the callback returns and 
127             * uncommited transactions are rolled back. Be careful of returning detached Hibernate 
128             * objects that may not be fully loaded with data; consider using projections / scalar 
129             * queries instead. <b>Note</b> This method uses a ManagedSessionContext. With JTA
130             * or other forms of current session lookup a wrapping session will not be
131             * detected and a new one will always be created. 
132             * @param unit work to be performed in thread-bound session
133             * @param key or null for the default factory
134             * @see SessionUnit
135             */
136            public static Object ensureSession(SessionUnit unit, Object key) {
137                    dataSessionRequested(key);
138                    SessionFactory sf = getHibernateSessionFactory(key);
139                    if (ManagedSessionContext.hasBind(sf))
140                            return unit.run(getHibernateSession(key));
141                    org.hibernate.classic.Session sess = sf.openSession();
142                    try {
143                            sess.beginTransaction();
144                            ManagedSessionContext.bind(sess);
145                            return unit.run(sess);
146                    } finally {
147                            try {
148                                    if (sess.getTransaction().isActive())
149                                            sess.getTransaction().rollback();
150                            } finally {
151                                    sess.close();
152                                    ManagedSessionContext.unbind(sf);
153                            }
154                    }
155            }
156    }