/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.transaction;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.neo4j.ogm.autoindex.MissingIndexException;
import org.neo4j.ogm.exception.*;
import org.neo4j.ogm.session.Neo4jException;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.exception.Neo4jErrorStatusCodes;
import org.springframework.data.neo4j.exception.UncategorizedNeo4jException;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Helper class featuring methods for Neo4j OGM Session handling,
* allowing for reuse of Session instances within transactions.
* Also provides support for exception translation.
* <p>Mainly intended for internal use within the framework.
*
* @author Mark Angrish
*/
public class SessionFactoryUtils {
private static final Logger logger = LoggerFactory.getLogger(SessionFactoryUtils.class);
public static void closeSession(Session session) {
}
public static Session getSession(SessionFactory sessionFactory) throws IllegalStateException {
Assert.notNull(sessionFactory, "No SessionFactory specified");
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if (sessionHolder != null) {
if (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
sessionHolder.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.registerSynchronization(
new SessionSynchronization(sessionHolder, sessionFactory, false));
}
return sessionHolder.getSession();
}
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
return null;
}
Session session = sessionFactory.openSession();
logger.debug("Registering transaction synchronization for Neo4j Session");
// Use same Session for further Neo4j actions within the transaction.
// Thread object will get removed by synchronization at transaction completion.
sessionHolder = new SessionHolder(session);
sessionHolder.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.registerSynchronization(
new SessionSynchronization(sessionHolder, sessionFactory, true));
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
return session;
}
/**
* Convert the given runtime exception to an appropriate exception from the
* {@code org.springframework.dao} hierarchy.
* Return null if no translation is appropriate: any other exception may
* have resulted from user code, and should not be translated.
*
* @param ex runtime exception that occurred
* @return the corresponding DataAccessException instance,
* or {@code null} if the exception should not be translated
*/
public static DataAccessException convertOgmAccessException(RuntimeException ex) {
// TODO: The OGM should not be throwing common runtime exceptions as these mask user errors.
// TODO: Instead we should be defining our own Exceptions that can then be translated into Spring Exceptions.
if (ex instanceof IllegalStateException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof NotFoundException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof InvalidDepthException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof ResultProcessingException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof MappingException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof UnknownStatementTypeException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof ConnectionException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MissingOperatorException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof ResultErrorsException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof ServiceNotFoundException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof TransactionException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof TransactionManagerException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof MissingIndexException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
if (ex instanceof Neo4jException) {
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
}
// All exceptions coming back from the database.
if (ex instanceof CypherException) {
String code = ((CypherException) ex).getCode();
final Class<? extends DataAccessException> dae = Neo4jErrorStatusCodes.translate(code);
if (dae != null) {
try {
final Constructor<? extends DataAccessException> constructor = dae.getDeclaredConstructor(String.class, Throwable.class);
return constructor.newInstance(ex.getMessage(), ex);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
return null;
}
}
return new UncategorizedNeo4jException(ex.getMessage(), ex);
}
// If we get here, we have an exception that resulted from user code,
// rather than the persistence provider, so we return null to indicate
// that translation should not occur.
return null;
}
private static class SessionSynchronization
extends ResourceHolderSynchronization<SessionHolder, SessionFactory>
implements Ordered {
private final boolean newSession;
SessionSynchronization(
SessionHolder sessionHolder, SessionFactory sessionFactory, boolean newSession) {
super(sessionHolder, sessionFactory);
this.newSession = newSession;
}
@Override
public int getOrder() {
return 900;
}
@Override
public void flushResource(SessionHolder resourceHolder) {
}
@Override
protected boolean shouldUnbindAtCompletion() {
return this.newSession;
}
@Override
protected boolean shouldReleaseAfterCompletion(SessionHolder resourceHolder) {
// return !resourceHolder.getSession().isClosed();
return false;
}
}
}