package org.springmodules.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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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
*
*/
public abstract class SessionFactoryUtils {
private static final Log logger = LogFactory.getLog(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");
}
logger.debug("Opening JCR Session");
Session session = sessionFactory.getSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.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)) {
logger.debug("Closing JCR Session");
session.logout();
}
}
/**
* Jcr exception translator - it converts specific JSR-170 checked exceptions into
* unchecked Spring DA exception.
*
* @author Guillaume Bort <guillaume.bort@zenexity.fr>
* @author Costin Leau
*
* @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);
}
}
}