/** * This file is part of mycollab-jackrabbit. * * mycollab-jackrabbit is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mycollab-jackrabbit 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mycollab-jackrabbit. If not, see <http://www.gnu.org/licenses/>. */ package org.springframework.extensions.jcr; import java.io.IOException; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidSerializedDataException; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.LoginException; import javax.jcr.MergeException; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.PathNotFoundException; import javax.jcr.ReferentialIntegrityException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.query.InvalidQueryException; import javax.jcr.version.VersionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; /** * FactoryBean for instantiating a Java Content Repository. This abstract class adds custom functionality * subclasses handling only the configuration issues. * @author Costin Leau * @author Sergio Bossa * @author Salvatore Incandela */ public abstract class SessionFactoryUtils { private static final Logger LOG = LoggerFactory.getLogger(SessionFactoryUtils.class); /** * Get a JCR Session for the given Repository. Is aware of and will return any existing corresponding * Session bound to the current thread, for example when using JcrTransactionManager. Same as * <code>getSession</code> but throws the original Repository. * @param sessionFactory Jcr Repository to create session with * @param allowCreate if a non-transactional Session should be created when no transactional Session can * be found for the current thread * @throws RepositoryException * @return */ public static Session doGetSession(SessionFactory sessionFactory, boolean allowCreate) throws RepositoryException { Assert.notNull(sessionFactory, "No sessionFactory specified"); // check if there is any transaction going on SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (sessionHolder != null && sessionHolder.getSession() != null) return sessionHolder.getSession(); if (!allowCreate && !TransactionSynchronizationManager.isSynchronizationActive()) { throw new IllegalStateException("No session bound to thread, " + "and configuration does not allow creation of non-transactional one here"); } LOG.debug("Opening JCR Session"); Session session = sessionFactory.getSession(); if (TransactionSynchronizationManager.isSynchronizationActive()) { LOG.debug("Registering transaction synchronization for JCR session"); // Use same session for further JCR actions within the transaction // thread object will get removed by synchronization at transaction // completion. sessionHolder = sessionFactory.getSessionHolder(session); sessionHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.registerSynchronization(new JcrSessionSynchronization(sessionHolder, sessionFactory)); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); } return session; } /** * Get a JCR Session for the given Repository. Is aware of and will return any existing corresponding * Session bound to the current thread, for example when using JcrTransactionManager. Will create a new * Session otherwise, if allowCreate is true. This is the getSession method used by typical data access * code, in combination with releaseSession called when done with the Session. Note that JcrTemplate * allows to write data access code without caring about such resource handling. Supports synchronization * with both Spring-managed JTA transactions (i.e. JtaTransactionManager) and non-Spring JTA transactions * (i.e. plain JTA or EJB CMT). * @param sessionFactory JCR Repository to create session with * @param allowCreate if a non-transactional Session should be created when no transactional Session can * be found for the current thread * @throws DataAccessException * @return */ public static Session getSession(SessionFactory sessionFactory, boolean allowCreate) throws DataAccessException { try { return doGetSession(sessionFactory, allowCreate); } catch (RepositoryException ex) { throw new DataAccessResourceFailureException("Could not open Jcr Session", ex); } } /** * Return whether the given JCR Session is thread-bound that is, bound to the current thread by Spring's * transaction facilities (which is used as a thread-bounding utility class). * @param session the JCR Session to check * @param sessionFactory the JCR SessionFactory that the Session was created with (can be null) * @return whether the Session is transactional */ public static boolean isSessionThreadBound(Session session, SessionFactory sessionFactory) { if (sessionFactory == null) { return false; } SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); return (sessionHolder != null && session == sessionHolder.getSession()); } /** * Close the given Session, created via the given repository, if it is not managed externally (i.e. not * bound to the thread). * @param session the Jcr Session to close * @param sessionFactory JcrSessionFactory that the Session was created with (can be null) */ public static void releaseSession(Session session, SessionFactory sessionFactory) { if (session == null) { return; } // Only close non thread bound Sessions. if (!isSessionThreadBound(session, sessionFactory)) { LOG.debug("Closing JCR Session"); session.logout(); } } /** * Jcr exception translator - it converts specific JSR-170 checked exceptions into unchecked Spring DA * exception. * @author Guillaume Bort <[email protected]> * @author Costin Leau * @author Sergio Bossa * @author Salvatore Incandela * @param ex * @return */ public static DataAccessException translateException(RepositoryException ex) { if (ex instanceof AccessDeniedException) { return new DataRetrievalFailureException("Access denied to this data", ex); } if (ex instanceof ConstraintViolationException) { return new DataIntegrityViolationException("Constraint has been violated", ex); } if (ex instanceof InvalidItemStateException) { return new ConcurrencyFailureException("Invalid item state", ex); } if (ex instanceof InvalidQueryException) { return new DataRetrievalFailureException("Invalid query", ex); } if (ex instanceof InvalidSerializedDataException) { return new DataRetrievalFailureException("Invalid serialized data", ex); } if (ex instanceof ItemExistsException) { return new DataIntegrityViolationException("An item already exists", ex); } if (ex instanceof ItemNotFoundException) { return new DataRetrievalFailureException("Item not found", ex); } if (ex instanceof LoginException) { return new DataAccessResourceFailureException("Bad login", ex); } if (ex instanceof LockException) { return new ConcurrencyFailureException("Item is locked", ex); } if (ex instanceof MergeException) { return new DataIntegrityViolationException("Merge failed", ex); } if (ex instanceof NamespaceException) { return new InvalidDataAccessApiUsageException("Namespace not registred", ex); } if (ex instanceof NoSuchNodeTypeException) { return new InvalidDataAccessApiUsageException("No such node type", ex); } if (ex instanceof NoSuchWorkspaceException) { return new DataAccessResourceFailureException("Workspace not found", ex); } if (ex instanceof PathNotFoundException) { return new DataRetrievalFailureException("Path not found", ex); } if (ex instanceof ReferentialIntegrityException) { return new DataIntegrityViolationException("Referential integrity violated", ex); } if (ex instanceof UnsupportedRepositoryOperationException) { return new InvalidDataAccessApiUsageException("Unsupported operation", ex); } if (ex instanceof ValueFormatException) { return new InvalidDataAccessApiUsageException("Incorrect value format", ex); } if (ex instanceof VersionException) { return new DataIntegrityViolationException("Invalid version graph operation", ex); } // fallback return new JcrSystemException(ex); } /** * Jcr exception translator - it converts specific JSR-170 checked exceptions into unchecked Spring DA * exception. * @param ex * @return */ public static DataAccessException translateException(IOException ex) { return new DataAccessResourceFailureException("I/O failure", ex); } /** * Callback for resource cleanup at the end of a non-JCR transaction (e.g. when participating in a * JtaTransactionManager transaction). * @see org.springframework.transaction.jta.JtaTransactionManager */ private static class JcrSessionSynchronization extends TransactionSynchronizationAdapter { private final SessionHolder sessionHolder; private final SessionFactory sessionFactory; private boolean holderActive = true; /** * @param sessionFactory * @param holder */ public JcrSessionSynchronization(SessionHolder holder, SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; sessionHolder = holder; } public void suspend() { if (this.holderActive) { TransactionSynchronizationManager.unbindResource(this.sessionFactory); } } public void resume() { if (this.holderActive) { TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder); } } public void beforeCompletion() { TransactionSynchronizationManager.unbindResource(this.sessionFactory); this.holderActive = false; releaseSession(this.sessionHolder.getSession(), this.sessionFactory); } } }