Created
May 26, 2014 07:36
-
-
Save ALRubinger/b584065d0e7da251469a to your computer and use it in GitHub Desktop.
Using Neo4j With an Existing TransactionManager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This is explained further in https://stackoverflow.com/questions/23824518/how-to-enlist-xaresource-with-existing-transaction/23865229#23865229