/**
* Copyright (C) 2011 JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jtalks.poulpe.web.osod;
import com.google.common.annotations.VisibleForTesting;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A class that controls the Hibernate Sessions - starts, finishes, clears them, etc. It's responsible for binding
* sessions to current thread so that Spring Transaction Manager can find the already opened session and use it to work
* with hibernate. This class is usually used by {@link OpenSessionOnDesktopZkListener} out of box, but if you want to
* manually influence the Hibernate Session (e.g. you want to clear it because of memory leaks), you can work with this
* object directly.
*
* @author stanislav bashkirtsev
* @see OpenSessionOnDesktopZkListener
*/
public class OpenSessions {
private final ConcurrentMap<String, Session> sessions = new ConcurrentHashMap<String, Session>();
private final SessionFactory sessionFactory;
/** Constructor for initialization variables */
public OpenSessions(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* Opens a new session and binds it to current thread. Results in no-op if there is already a session bound. Binding
* to the tread means saving the session as a {@link ThreadLocal} variable for the thread so that any class at any
* point can access that variable (particularly Spring Transaction related classes) within a single thread, it won't
* be visible to other threads.
*
* @param desktopId a ZK desktop ID to associate session with, later it will be possible to close session having
* this id
*/
public void openSession(String desktopId) {
if (noSessionBoundToThread()) {
Session session = getOrCreateSession(desktopId);
sessions.putIfAbsent(desktopId, session);
bindToThread(session);
}
}
/**
* Unbinds (removes from the {@link ThreadLocal} variables of the thread) the session which means that Spring
* Transaction related classes won't have access to the session anymore. This usually should be done when request
* processing is finished. This doesn't close the session, it still there and is associated with desktop id.
*/
public void unbindSession() {
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
/**
* Closes the session inside the {@link OpenSessions} so that it won't ever be reusable. If some class of this
* session is going to access to lazy fields, it will result in {@link org.hibernate.LazyInitializationException}.
* This usually should be done when the desktop of the user is going to be recycled (e.g. page refresh).
*
* @param desktopId the id of the desktop to find an associated session for, if there is no session found for this
* id, method will result in no-op
*/
public void closeSession(String desktopId) {
Session session = sessions.remove(desktopId);
if (session != null) {
session.close();
}
}
/**
* Creates a new session for the specified desktop id if doesn't exist. If it's already inside {@link OpenSessions},
* then it will be returned. It also sets the flush mode to {@link FlushMode#MANUAL} when session is opened. This
* method doesn't bind sessions to the thread.
*
* @param desktopId an id of the desktop to search the session for
* @return a session that is bound to the desktop or new session that is not bound to anything if no desktop with
* such id was registered
*/
@VisibleForTesting
Session getOrCreateSession(String desktopId) {
Session session = sessions.get(desktopId);
if (session == null) {
session = createSession();
session.setFlushMode(FlushMode.MANUAL);
}
return session;
}
/** Creates new hibernate session */
@VisibleForTesting
Session createSession() {
return SessionFactoryUtils.getSession(sessionFactory, true);
}
/** Binds hibernate session to current thread
* @param session for binding
*/
@VisibleForTesting
void bindToThread(Session session) {
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
/** Checks in current thread binded hibernate session */
@VisibleForTesting
boolean noSessionBoundToThread() {
return !TransactionSynchronizationManager.hasResource(sessionFactory);
}
}