Skip to content

Instantly share code, notes, and snippets.

@ALRubinger
Created May 26, 2014 07:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ALRubinger/b584065d0e7da251469a to your computer and use it in GitHub Desktop.
Save ALRubinger/b584065d0e7da251469a to your computer and use it in GitHub Desktop.
Using Neo4j With an Existing TransactionManager
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.neo4j2.Neo4j2Graph;
import org.jboss.metrics.playground.persistence.Neo4jExternalTransactionManager;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.kernel.GraphDatabaseAPI;
import javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Test case to ensure that the Narayana {@link javax.transaction.TransactionManager}
* may be used with Neo4j and that the DB will respect its semantics
*
* @author <a href="mailto:alr@jboss.org">Andrew Lee Rubinger</a>
*/
public class NarayanaNeo4jTestCase {
private static final Logger log = Logger.getLogger(NarayanaNeo4jTestCase.class.getName());
private static final String FILE_NAME_STORAGE = "build/neo4j";
@BeforeClass
public static void configureTM() {
// TODO Is this having any effect on the TM? Check w/ Narayana team; this came from an *old* quickstart
System.setProperty("ObjectStoreBaseDir", "build");
}
@AfterClass
public static void shutdown() {
// Remove the datastore
final File datastoreFile = new File(FILE_NAME_STORAGE);
recursiveRemove(datastoreFile);
}
/**
* Recursively remove this reference and all children
*
* @param file
*/
private static void recursiveRemove(final File file) {
assert file != null;
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
for (final File child : file.listFiles()) {
recursiveRemove(child);
}
}
final boolean deleted = file.delete();
log.log(Level.FINER, "Deleted file '" + file.getAbsolutePath() + ": " + deleted);
}
@Test
public void rollbackShouldNotPersist() throws Exception {
// Get a TM to mock actions the container would take
final TransactionManager underlyingTm = com.arjuna.ats.jta.TransactionManager
.transactionManager();
// Set Tx in test context
TestContext.TRANSACTION_MANAGER.set(underlyingTm);
// Create the DB
final GraphDatabaseService graphDatabaseService = new GraphDatabaseFactory().
newEmbeddedDatabaseBuilder(FILE_NAME_STORAGE).setConfig(
GraphDatabaseSettings.tx_manager_impl.name(),
Neo4jExternalTransactionManager.Provider.NAME)
.newGraphDatabase();
// Get a handle on the Neo4j view wrapping the TM
/*
* Note: Is there a better way to implement this, where Neo4j will simply
* use the existing TM, or supply an XAResource with which we may enlist in
* our own TM? Sure, this mechanism gets us using the underlying TM that we supply,
* but we still must interact with the Neo4j TM, not ours, to begin transactions.
*/
final TransactionManager tm = ((GraphDatabaseAPI) graphDatabaseService).getDependencyResolver().resolveDependency(TransactionManager.class);
tm.begin();
// Get a Transaction
final Transaction tx = tm.getTransaction();
// Get a Tinkerpop Blueprints API view of the DB
final Graph graph = new Neo4j2Graph(graphDatabaseService);
// Create some thing in the DB
final Vertex createdVertex = graph.addVertex(1);
final Object id = createdVertex.getId();
log.info("Created: " + createdVertex + " with ID: " + id);
// Set the TX to rollback (like a user would)
tx.setRollbackOnly();
// Attempt to find the object
final Object roundtrip = graph.getVertex(id);
log.info("Found: " + roundtrip);
Assert.assertNotNull("Roundtrip object should be found before we rollback", roundtrip);
// Have the TM attempt to perform the commit (like the container would;
// this should fail on account of having set rollback only)
boolean gotRollback = false;
try {
tm.commit();
} catch (final RollbackException expected) {
gotRollback = true;
log.log(Level.FINER, "Got expected rollback");
}
Assert.assertTrue("rollback should have occurred; improper test setup", gotRollback);
// Start a new Tx
tm.begin();
final Transaction newTx = tm.getTransaction();
// Check that the DB has not no reference to what was just created
final Object roundtripAfterRollback = graph.getVertex(id);
newTx.commit();
log.info("On attempt to get previously-created ID " + id + " after a rollback: " + roundtripAfterRollback);
Assert.assertNull("Should not be able to find reference to object after rollback", roundtripAfterRollback);
}
}
import org.neo4j.kernel.impl.core.KernelPanicEventGenerator;
import org.neo4j.kernel.impl.core.TransactionState;
import org.neo4j.kernel.impl.nioneo.store.FileSystemAbstraction;
import org.neo4j.kernel.impl.transaction.AbstractTransactionManager;
import org.neo4j.kernel.impl.transaction.RemoteTxHook;
import org.neo4j.kernel.impl.transaction.TransactionManagerProvider;
import org.neo4j.kernel.impl.transaction.TransactionStateFactory;
import org.neo4j.kernel.impl.transaction.XaDataSourceManager;
import org.neo4j.kernel.impl.util.StringLogger;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.HashMap;
import java.util.Map;
/**
* Adaptor for an external transaction manager to be used by Neo4j. Note that
* it's *this* {@link javax.transaction.TransactionManager} implementation
* which must be used by calling code, and not the underlying delegate
* to be called directly, else Neo4j will not respect the association. In essence
* this class must wrap the desired delegate {@link javax.transaction.TransactionManager}.
*
* @author <a href="mailto:alr@jboss.org">Andrew Lee Rubinger</a>
*/
public class Neo4jExternalTransactionManager extends AbstractTransactionManager {
/**
* Underlying implementation to which all
* {@link javax.transaction.TransactionManager} methods delegate
*/
private final TransactionManager delegate;
/**
* Map of {@link javax.transaction.Transaction} to the states they're in
*/
private final Map<Transaction, TransactionState> txStates = new HashMap<>();
private final TransactionStateFactory transactionStateFactory;
/**
* Neo4j hook to use an external {@link javax.transaction.TransactionManager)
* implementation; designated by a file named
* {@code META-INF/services/org.neo4j.kernel.impl.transaction.TransactionManagerProvider}
* with contents equal to this FQN
*/
public static class Provider extends TransactionManagerProvider {
public static final String NAME = Neo4jExternalTransactionManager.Provider.class.getName();
public Provider() {
super(NAME);
}
@Override
public AbstractTransactionManager loadTransactionManager(
final String txLogDir,
final XaDataSourceManager xaDataSourceManager,
final KernelPanicEventGenerator kpe,
final RemoteTxHook rollbackHook,
final StringLogger msgLog,
final FileSystemAbstraction fileSystem,
final TransactionStateFactory stateFactory) {
// Pick up the underlying transaction manager from some known context
final TransactionManager delegate = TestContext.TRANSACTION_MANAGER.get();
if (delegate == null) {
throw new RuntimeException("must have tx manager set in " + TestContext.class.getName());
}
return new Neo4jExternalTransactionManager(delegate, stateFactory);
}
}
/**
* Internal ctor; instances created by Neo4j via the entry point
* {@link Neo4jExternalTransactionManager.Provider#loadTransactionManager(
*String,
* org.neo4j.kernel.impl.transaction.XaDataSourceManager,
* org.neo4j.kernel.impl.core.KernelPanicEventGenerator,
* org.neo4j.kernel.impl.transaction.RemoteTxHook,
* org.neo4j.kernel.impl.util.StringLogger,
* org.neo4j.kernel.impl.nioneo.store.FileSystemAbstraction,
* org.neo4j.kernel.impl.transaction.TransactionStateFactory)}
*
* @param delegate
*/
private Neo4jExternalTransactionManager(final TransactionManager delegate, final TransactionStateFactory transactionStateFactory) {
assert delegate != null : "tx manager delegate must be specified";
assert transactionStateFactory != null : "tx state factory must be specified";
this.delegate = delegate;
this.transactionStateFactory = transactionStateFactory;
}
/*
* Delegate methods
*/
/**
* Here we have to note the state that this current transaction is in to avoid Neo4j
* from attempting to enlist the XAResource twice and encountering an exception upon doing so
*
* @throws NotSupportedException
* @throws SystemException
*/
@Override
public void begin() throws NotSupportedException, SystemException {
delegate.begin();
Transaction tx = getTransaction();
txStates.put(tx, transactionStateFactory.create(tx));
}
@Override
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException {
delegate.commit();
}
@Override
public void rollback() throws IllegalStateException, SecurityException, SystemException {
delegate.rollback();
}
@Override
public void setRollbackOnly() throws IllegalStateException, SystemException {
delegate.setRollbackOnly();
}
@Override
public int getStatus() throws SystemException {
return delegate.getStatus();
}
@Override
public void setTransactionTimeout(final int seconds) throws SystemException {
delegate.setTransactionTimeout(seconds);
}
@Override
public Transaction getTransaction() throws SystemException {
return delegate.getTransaction();
}
@Override
public Transaction suspend() throws SystemException {
return delegate.suspend();
}
@Override
public void resume(final Transaction tobj) throws InvalidTransactionException, IllegalStateException, SystemException {
delegate.resume(tobj);
}
@Override
public void doRecovery() throws Throwable {
}
@Override
public TransactionState getTransactionState() {
try {
final TransactionState state = txStates.get(getTransaction());
return state != null ? state : TransactionState.NO_STATE;
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
@Override
public int getEventIdentifier() {
return 0; // Undocumented what this is supposed to do
}
@Override
public void init() throws Throwable {
// NOOP, Tx Manager is not managed by Neo4j lifecycle
}
@Override
public void start() throws Throwable {
// NOOP, Tx Manager is not managed by Neo4j lifecycle
}
@Override
public void stop() throws Throwable {
// NOOP, Tx Manager is not managed by Neo4j lifecycle
}
@Override
public void shutdown() throws Throwable {
// NOOP, Tx Manager is not managed by Neo4j lifecycle
}
}
org.whatever.FullyQualifiedClassNameOfYourTransactionManagerProviderImpl (in my case, it'd be Neo4jExternalTransactionManager$Provider) (Place in META-INF/services)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment