/* * Copyright (c) 2002-2009 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.transaction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import javax.transaction.TransactionManager; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.junit.Before; import org.junit.Test; import org.neo4j.graphdb.Node; import org.neo4j.kernel.impl.AbstractNeo4jTestCase; import org.neo4j.kernel.impl.transaction.TxModule; import org.neo4j.kernel.impl.transaction.XaDataSourceManager; import org.neo4j.kernel.impl.transaction.XidImpl; import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer; import org.neo4j.kernel.impl.transaction.xaframework.XaCommand; import org.neo4j.kernel.impl.transaction.xaframework.XaCommandFactory; import org.neo4j.kernel.impl.transaction.xaframework.XaConnection; import org.neo4j.kernel.impl.transaction.xaframework.XaConnectionHelpImpl; import org.neo4j.kernel.impl.transaction.xaframework.XaContainer; import org.neo4j.kernel.impl.transaction.xaframework.XaDataSource; import org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog; import org.neo4j.kernel.impl.transaction.xaframework.XaResourceHelpImpl; import org.neo4j.kernel.impl.transaction.xaframework.XaResourceManager; import org.neo4j.kernel.impl.transaction.xaframework.XaTransaction; import org.neo4j.kernel.impl.transaction.xaframework.XaTransactionFactory; public class TestXaFramework extends AbstractNeo4jTestCase { private TransactionManager tm; private XaDataSourceManager xaDsMgr; private String path() { String path = getStorePath( "xafrmwrk" ); new File( path ).mkdirs(); return path; } private String file( String name ) { return path() + File.separator + name; } private String resourceFile() { return file( "dummy_resource" ); } @Before public void setUpFramework() { getTransaction().finish(); TxModule txModule = getEmbeddedGraphDb().getConfig().getTxModule(); tm = txModule.getTxManager(); xaDsMgr = txModule.getXaDataSourceManager(); } private static class DummyCommand extends XaCommand { private int type = -1; DummyCommand( int type ) { this.type = type; } public void execute() { } // public void writeToFile( FileChannel fileChannel, ByteBuffer buffer ) // throws IOException public void writeToFile( LogBuffer buffer ) throws IOException { // buffer.clear(); buffer.putInt( type ); // buffer.flip(); // fileChannel.write( buffer ); } } private static class DummyCommandFactory extends XaCommandFactory { public XaCommand readCommand( ReadableByteChannel byteChannel, ByteBuffer buffer ) throws IOException { buffer.clear(); buffer.limit( 4 ); if ( byteChannel.read( buffer ) == 4 ) { buffer.flip(); return new DummyCommand( buffer.getInt() ); } return null; } } private static class DummyTransaction extends XaTransaction { private java.util.List<XaCommand> commandList = new java.util.ArrayList<XaCommand>(); public DummyTransaction( int identifier, XaLogicalLog log ) { super( identifier, log ); } public void doAddCommand( XaCommand command ) { commandList.add( command ); } // public XaCommand[] getCommands() // { // return commandList.toArray( new XaCommand[commandList.size()] ); // } public void doPrepare() { } public void doRollback() { } public void doCommit() { } public boolean isReadOnly() { return false; } } private static class DummyTransactionFactory extends XaTransactionFactory { public XaTransaction create( int identifier ) { return new DummyTransaction( identifier, getLogicalLog() ); } public void flushAll() { } @Override public long getAndSetNewVersion() { return -1; } @Override public long getCurrentVersion() { return -1; } } public class DummyXaDataSource extends XaDataSource { private XaContainer xaContainer = null; public DummyXaDataSource( java.util.Map<?,?> map ) throws InstantiationException { super( map ); try { xaContainer = XaContainer.create( resourceFile(), new DummyCommandFactory(), new DummyTransactionFactory(), null ); xaContainer.openLogicalLog(); } catch ( IOException e ) { throw new InstantiationException( "" + e ); } } public void close() { xaContainer.close(); // cleanup dummy resource log File dir = new File( "." ); File files[] = dir.listFiles( new FilenameFilter() { public boolean accept( File dir, String fileName ) { return fileName.startsWith( resourceFile() ); } } ); for ( int i = 0; i < files.length; i++ ) { files[i].delete(); } } public XaConnection getXaConnection() { return new DummyXaConnection( xaContainer.getResourceManager() ); } @Override public byte[] getBranchId() { // TODO Auto-generated method stub return null; } @Override public void setBranchId( byte[] branchId ) { // TODO Auto-generated method stub } } private static class DummyXaResource extends XaResourceHelpImpl { DummyXaResource( XaResourceManager xaRm ) { super( xaRm, null ); } public boolean isSameRM( XAResource resource ) { if ( resource instanceof DummyXaResource ) { return true; } return false; } } private class DummyXaConnection extends XaConnectionHelpImpl { private XAResource xaResource = null; public DummyXaConnection( XaResourceManager xaRm ) { super( xaRm ); xaResource = new DummyXaResource( xaRm ); } public XAResource getXaResource() { return xaResource; } public void doStuff1() throws XAException { validate(); getTransaction().addCommand( new DummyCommand( 1 ) ); } public void doStuff2() throws XAException { validate(); getTransaction().addCommand( new DummyCommand( 2 ) ); } public void enlistWithTx() throws Exception { tm.getTransaction().enlistResource( xaResource ); } public void delistFromTx() throws Exception { tm.getTransaction().delistResource( xaResource, XAResource.TMSUCCESS ); } public int getTransactionId() throws Exception { return getTransaction().getIdentifier(); } } @Test public void testCreateXaResource() throws Exception { xaDsMgr.registerDataSource( "dummy_datasource", new DummyXaDataSource( new java.util.HashMap<Object, Object>() ), "DDDDDD".getBytes() ); XaDataSource xaDs = xaDsMgr.getXaDataSource( "dummy_datasource" ); DummyXaConnection xaC = null; try { xaC = (DummyXaConnection) xaDs.getXaConnection(); try { xaC.doStuff1(); fail( "Non enlisted resource should throw exception" ); } catch ( XAException e ) { // good } Xid xid = new XidImpl( new byte[0], new byte[0] ); xaC.getXaResource().start( xid, XAResource.TMNOFLAGS ); try { xaC.doStuff1(); xaC.doStuff2(); } catch ( XAException e ) { fail( "Enlisted resource should not throw exception" ); } xaC.getXaResource().end( xid, XAResource.TMSUCCESS ); xaC.getXaResource().prepare( xid ); xaC.getXaResource().commit( xid, false ); } finally { xaDsMgr.unregisterDataSource( "dummy_datasource" ); if ( xaC != null ) { xaC.destroy(); } } // cleanup dummy resource log File dir = new File( "." ); File files[] = dir.listFiles( new FilenameFilter() { public boolean accept( File dir, String fileName ) { return fileName.startsWith( resourceFile() ); } } ); for ( int i = 0; i < files.length; i++ ) { files[i].delete(); } } @Test public void testTxIdGeneration() throws Exception { DummyXaDataSource xaDs1 = null; DummyXaConnection xaC1 = null; try { xaDsMgr.registerDataSource( "dummy_datasource1", new DummyXaDataSource( new java.util.HashMap<Object, Object>() ), "DDDDDD".getBytes() ); xaDs1 = (DummyXaDataSource) xaDsMgr .getXaDataSource( "dummy_datasource1" ); xaC1 = (DummyXaConnection) xaDs1.getXaConnection(); tm.begin(); // get xaC1.enlistWithTx(); int currentTxId = xaC1.getTransactionId(); xaC1.doStuff1(); xaC1.delistFromTx(); tm.commit(); // xaC2 = ( DummyXaConnection ) xaDs2.getXaConnection(); tm.begin(); Node node = getGraphDb().createNode(); // get resource in tx xaC1.enlistWithTx(); assertEquals( ++currentTxId, xaC1.getTransactionId() ); xaC1.doStuff1(); xaC1.delistFromTx(); tm.commit(); tm.begin(); node = getGraphDb().getNodeById( (int) node.getId() ); xaC1.enlistWithTx(); assertEquals( ++currentTxId, xaC1.getTransactionId() ); xaC1.doStuff2(); xaC1.delistFromTx(); node.delete(); tm.commit(); } finally { xaDsMgr.unregisterDataSource( "dummy_datasource1" ); // xaDsMgr.unregisterDataSource( "dummy_datasource1" ); if ( xaC1 != null ) { xaC1.destroy(); } } // cleanup dummy resource log File dir = new File( "." ); File files[] = dir.listFiles( new FilenameFilter() { public boolean accept( File dir, String fileName ) { return fileName.startsWith( resourceFile() ); } } ); for ( int i = 0; i < files.length; i++ ) { files[i].delete(); } } }