diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/TransactionalRemoteStatelessTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/TransactionalRemoteStatelessTestCase.java
new file mode 100644
index 000000000000..a7a332720dac
--- /dev/null
+++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/TransactionalRemoteStatelessTestCase.java
@@ -0,0 +1,449 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2019, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.as.test.clustering.cluster.ejb.remote;
+
+import java.util.Hashtable;
+import java.util.PropertyPermission;
+
+import javax.ejb.NoSuchEJBException;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.transaction.Status;
+import javax.transaction.UserTransaction;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.as.arquillian.container.ManagementClient;
+import org.jboss.as.test.clustering.cluster.AbstractClusteringTestCase;
+import org.jboss.as.test.clustering.cluster.ejb.remote.bean.Incrementor;
+import org.jboss.as.test.clustering.cluster.ejb.remote.bean.IncrementorBean;
+import org.jboss.as.test.clustering.cluster.ejb.remote.bean.Result;
+import org.jboss.as.test.clustering.cluster.ejb.remote.bean.StatelessTransactionBean;
+import org.jboss.as.test.clustering.cluster.ejb.remote.bean.StatelessTransactionNoResourceBean;
+import org.jboss.as.test.clustering.ejb.EJBDirectory;
+import org.jboss.as.test.integration.transactions.TransactionCheckerSingleton;
+import org.jboss.as.test.integration.transactions.TransactionCheckerSingletonRemote;
+import org.jboss.as.test.integration.transactions.TransactionTestLookupUtil;
+import org.jboss.as.test.shared.integration.ejb.security.PermissionUtils;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ *
+ * This is a set of tests which verify how transaction affinity on standalone EJB client
+ * works when client communicates with cluster.
+ * When transaction is started then all EJB calls has to be strongly bound to the server
+ * that the first call was run at.
+ * A note: the subordinate transaction is not enlisted until it's used. It means the
+ * txn.begin()
does define affinity it's defined at time the bean call is run.
+ *
+ *
+ * The client uses three different ways to define destination of the communication,
+ * a.k.a. to define URL of the cluster where it aims to connect to. The test runs the three options:
+ *
+ * wildfly-config.xml
+ * - during InitialContext creation, using
remote+http
protocol
+ * - during InitialContext creation, using
http
protocol
+ * (see wildfly/wildfly-http-client
+ *
+ *
+ */
+@RunWith(Arquillian.class)
+@RunAsClient
+public class TransactionalRemoteStatelessTestCase extends AbstractClusteringTestCase {
+ private static final String MODULE_NAME = TransactionalRemoteStatelessTestCase.class.getSimpleName();
+
+ private int node0Port = 8080;
+ private int node1Port = 8180;
+
+ private TransactionCheckerSingletonRemote checkerNode0, checkerNode1;
+
+ private static enum InitialContextLookupType {
+ REMOTE_HTTP, HTTP, WILDFLY_CONFIG
+ }
+
+ @Deployment(name = DEPLOYMENT_1, managed = false, testable = false)
+ @TargetsContainer(NODE_1)
+ public static Archive> createDeploymentForContainer1() {
+ return createDeployment();
+ }
+
+ @Deployment(name = DEPLOYMENT_2, managed = false, testable = false)
+ @TargetsContainer(NODE_2)
+ public static Archive> createDeploymentForContainer2() {
+ return createDeployment();
+ }
+
+ private static Archive> createDeployment() {
+ return ShrinkWrap.create(JavaArchive.class, MODULE_NAME + ".jar")
+ .addPackage(EJBDirectory.class.getPackage())
+ .addClasses(Result.class, Incrementor.class, IncrementorBean.class)
+ .addClasses(StatelessTransactionBean.class, StatelessTransactionNoResourceBean.class)
+ .addPackage(org.jboss.as.test.integration.transactions.TestXAResource.class.getPackage())
+ .addAsManifestResource(PermissionUtils.createPermissionsXmlAsset(
+ new PropertyPermission(NODE_NAME_PROPERTY, "read")), "permissions.xml");
+ }
+
+ @Before
+ public void setUp() {
+ try {
+ checkerNode0 = TransactionTestLookupUtil.lookupEjbStateless(
+ TESTSUITE_NODE0, node0Port, MODULE_NAME,
+ TransactionCheckerSingleton.class, TransactionCheckerSingletonRemote.class);
+ checkerNode1 = TransactionTestLookupUtil.lookupEjbStateless(
+ TESTSUITE_NODE1, node1Port, MODULE_NAME,
+ TransactionCheckerSingleton.class, TransactionCheckerSingletonRemote.class);
+ } catch (NamingException e) {
+ new IllegalStateException(String.format("Cannot find singleton for checking "
+ + "transaction state at '%s:%s'", TESTSUITE_NODE0, node0Port));
+ }
+ checkerNode0.resetAll();
+ checkerNode1.resetAll();
+
+ checkAndCleanTransactionState();
+ }
+
+ private void checkAndCleanTransactionState() {
+ try {
+ InitialContext initCtx = getInitialContext(
+ InitialContextLookupType.WILDFLY_CONFIG, TESTSUITE_NODE0);
+ UserTransaction txn = getUserTransaction(initCtx);
+ if(txn.getStatus() == Status.STATUS_ACTIVE) {
+ txn.rollback();
+ }
+ } catch (Exception e) {
+ log.errorf("Cannot check state and clean transaction to be obtained "
+ + "from server defined in wildfly-config.xml", e);
+ }
+ }
+
+ @Test
+ public void affinityCommit_remoteHttp() throws Exception {
+ affinityCommit(InitialContextLookupType.REMOTE_HTTP);
+ }
+
+ @Test
+ public void affinityCommit_http() throws Exception {
+ affinityCommit(InitialContextLookupType.HTTP);
+ }
+
+ @Test
+ public void affinityCommit_wildflyConfig() throws Exception {
+ affinityCommit(InitialContextLookupType.WILDFLY_CONFIG);
+ }
+
+ /**
+ * Looking-up for two different beans under single transaction context.
+ * The both beans should be called at the same node.
+ * The node can be arbitrary but it has to be the one defined.
+ */
+ private void affinityCommit(InitialContextLookupType lookupType) throws Exception {
+ InitialContext initCtx = getInitialContext(lookupType, TESTSUITE_NODE0);
+ String targetNode = null;
+ UserTransaction txn = null;
+
+ try {
+ txn = getUserTransaction(initCtx);
+
+ txn.begin();
+
+ Incrementor bean = getStatelessRemoteBean(initCtx, StatelessTransactionBean.class);
+ Result result = bean.increment();
+ Assert.assertEquals(1, result.getValue().intValue());
+
+ targetNode = result.getNode();
+
+ // lookup a different bean at the remote server while using the same context at client
+ InitialContext initCtx2 = getInitialContext(lookupType, TESTSUITE_NODE1);
+ Incrementor bean2 = getStatelessRemoteBean(initCtx2, StatelessTransactionNoResourceBean.class);
+ Result resultSecondCall = bean2.increment();
+ Assert.assertEquals(1, resultSecondCall.getValue().intValue());
+ Assert.assertEquals(targetNode, resultSecondCall.getNode());
+
+ txn.commit();
+ } catch (Exception e) {
+ if(txn != null) txn.rollback();
+ throw e;
+ } finally {
+ initCtx.close();
+ }
+
+ if(targetNode.equals(NODE_1)) {
+ Assert.assertEquals("Transaction affinity was to '" + NODE_1 + "' the commit should be processed there",
+ 1, checkerNode0.getCommitted());
+ Assert.assertEquals("Rollback at '" + NODE_1 + "' should not be called", 0, checkerNode0.getRolledback());
+ Assert.assertEquals("Two synchronizations were registered at '" + NODE_1 + "'",
+ 2, checkerNode0.countSynchronizedAfterCommitted());
+ Assert.assertEquals("Expected no commit to be run at " + NODE_2, 0, checkerNode1.getCommitted());
+ Assert.assertEquals("Expected no rollback to be run at " + NODE_2, 0, checkerNode1.getRolledback());
+ } else if (targetNode.equals(NODE_2)) {
+ Assert.assertEquals("Expected no commit to be run at " + NODE_1, 0, checkerNode0.getCommitted());
+ Assert.assertEquals("Expected no rollback to be run at " + NODE_1, 0, checkerNode0.getRolledback());
+ Assert.assertEquals("Transaction affinity was to '" + NODE_2 + "' the commit should be processed there",
+ 1, checkerNode1.getCommitted());
+ Assert.assertEquals("Rollback at '" + NODE_2 + "' should not be called", 0, checkerNode1.getRolledback());
+ Assert.assertEquals("Two synchronizations were registered at '" + NODE_2 + "'",
+ 2, checkerNode1.countSynchronizedAfterCommitted());
+ } else {
+ Assert.fail(String.format("Expecting one of the nodes [%s,%s] should be hit "
+ + "by the bean call but the target node was not expected '%s'",
+ NODE_1, NODE_2, targetNode));
+ }
+ }
+
+ /**
+ * Purpose of the test is to verify that remote+http
{@link InitialContext}
+ * lookup uses the URL hostname which is defined in the JNDI properties
+ * during the context creation. When non-existing URL is provided
+ * then the failure is expected.
+ */
+ @Test
+ public void directLookupFailure () throws Exception {
+ InitialContext initCtx = getInitialContext(InitialContextLookupType.REMOTE_HTTP, "non-existing-hostname");
+
+ try {
+ Incrementor bean = getStatelessRemoteBean(initCtx, StatelessTransactionBean.class);
+ bean.increment();
+ Assert.fail("Expected a Exception as the bean is not deployed at 'node-3'"
+ + "while the initial context points the lookup there");
+ } catch (org.jboss.ejb.client.RequestSendFailedException expected) {
+ // expected as the deployment was removed from the node
+ }
+ }
+
+ @Test
+ public void rollback_remoteHttp() throws Exception {
+ rollback(InitialContextLookupType.REMOTE_HTTP);
+ }
+
+ @Test
+ public void rollback_http() throws Exception {
+ rollback(InitialContextLookupType.HTTP);
+ }
+
+ @Test
+ public void rollback_wildflyConfig() throws Exception {
+ rollback(InitialContextLookupType.WILDFLY_CONFIG);
+ }
+
+ /**
+ * Checking if the rollback is commanded to the remote node
+ * when the remotely looked-up transaction is rolled-back on the client side.
+ */
+ private void rollback (InitialContextLookupType lookupType) throws Exception {
+ InitialContext initCtx = getInitialContext(lookupType, TESTSUITE_NODE0);
+ String targetNode = null;
+ UserTransaction txn = null;
+
+ try {
+ Incrementor bean = getStatelessRemoteBean(initCtx, StatelessTransactionBean.class);
+ txn = getUserTransaction(initCtx);
+
+ txn.begin();
+
+ Result result = bean.increment();
+ Assert.assertEquals(1, result.getValue().intValue());
+ targetNode = result.getNode();
+
+ txn.rollback();
+ } catch (Exception e) {
+ if(txn != null) txn.rollback();
+ throw e;
+ } finally {
+ initCtx.close();
+ }
+
+ if(targetNode.equals(NODE_1)) {
+ Assert.assertEquals("Commit at '" + NODE_1 + "' should not be called", 0, checkerNode0.getCommitted());
+ Assert.assertEquals("Transaction affinity was to '" + NODE_1 + "' the rollback should be processed there", 1, checkerNode0.getRolledback());
+ Assert.assertEquals("A synchronization was registered at '" + NODE_1 + "'", 1, checkerNode0.countSynchronizedAfterRolledBack());
+ Assert.assertEquals("Expected no commit to be run at " + NODE_2, 0, checkerNode1.getCommitted());
+ Assert.assertEquals("Expected no rollback to be run at " + NODE_2, 0, checkerNode1.getRolledback());
+ } else if (targetNode.equals(NODE_2)) {
+ Assert.assertEquals("Expected no commit to be run at " + NODE_1, 0, checkerNode0.getCommitted());
+ Assert.assertEquals("Expected no rollback to be run at " + NODE_1, 0, checkerNode0.getRolledback());
+ Assert.assertEquals("Commit at '" + NODE_2 + "' should not be called", 0, checkerNode1.getCommitted());
+ Assert.assertEquals("Transaction affinity was to '" + NODE_2 + "' the rollback should be processed there", 1, checkerNode1.getRolledback());
+ Assert.assertEquals("A synchronization was registered at '" + NODE_2 + "'", 1, checkerNode1.countSynchronizedAfterRolledBack());
+ } else {
+ Assert.fail(String.format("Expecting one of the nodes [%s,%s] should be hit "
+ + "by the bean call but the target node was not expected '%s'",
+ NODE_1, NODE_2, targetNode));
+ }
+ }
+
+ /**
+ * Rollback has to be run when commanded at the client side
+ * despite there is no real resource enlisted to the transaction at the server side.
+ */
+ @Test
+ public void rollbackWithNoXAResourceEnlistment () throws Exception {
+ InitialContext initCtx = getInitialContext(InitialContextLookupType.REMOTE_HTTP, TESTSUITE_NODE0);
+ Result result = null;
+ UserTransaction txn = null;
+
+ try {
+ Incrementor bean = getStatelessRemoteBean(initCtx, StatelessTransactionNoResourceBean.class);
+ txn = getUserTransaction(initCtx);
+
+ txn.begin();
+
+ result = bean.increment();
+ Assert.assertEquals(1, result.getValue().intValue());
+
+ txn.rollback();
+ } catch (Exception e) {
+ if(txn != null) txn.rollback();
+ throw e;
+ } finally {
+ initCtx.close();
+ }
+
+ if(result.getNode().equals(NODE_1)) {
+ Assert.assertFalse("Transaction was rolled-back at " + NODE_1 +
+ "Synchronization.beforeCompletion can't be called", checkerNode0.isSynchronizedBefore());
+ Assert.assertEquals("Transaction was rolled-back at " + NODE_1 +
+ ", after synchronization callback needs to be called", 1, checkerNode0.countSynchronizedAfterRolledBack());
+ Assert.assertFalse("Expected no beforeCompletion to be run at " + NODE_2, checkerNode1.isSynchronizedBefore());
+ Assert.assertFalse("Expected no afterCompletion to be run at " + NODE_2, checkerNode1.isSynchronizedAfter());
+ } else if (result.getNode().equals(NODE_2)) {
+ Assert.assertFalse("Expected no beforeCompletion to be run at " + NODE_1, checkerNode0.isSynchronizedBefore());
+ Assert.assertFalse("Expected no afterCompletion to be run at " + NODE_1, checkerNode0.isSynchronizedAfter());
+ Assert.assertFalse("Transaction was rolled-back at " + NODE_2 +
+ "Synchronization.beforeCompletion can't be called", checkerNode1.isSynchronizedBefore());
+ Assert.assertEquals("Transaction was rolled-back at " + NODE_2 +
+ ", after synchronization callback needs to be called", 1, checkerNode1.countSynchronizedAfterRolledBack());
+ } else {
+ Assert.fail(String.format("Expecting one of the nodes [%s,%s] should be hit "
+ + "by the bean call but the target node was not expected '%s'",
+ NODE_1, NODE_2, result.getNode()));
+ }
+ }
+
+ /**
+ * This is a similar test to
+ * {@link TransactionalRemoteStatefulEJBFailoverTestCase#test(ManagementClient, ManagementClient)}
+ * but this test works with a stateless bean.
+ */
+ @Test
+ public void affinityNodeFailure () throws Exception {
+ InitialContext initCtx = getInitialContext(InitialContextLookupType.REMOTE_HTTP, TESTSUITE_NODE0);
+ String targetNode = null;
+ UserTransaction txn = null;
+
+ try {
+ txn = getUserTransaction(initCtx);
+
+ txn.begin();
+
+ Incrementor bean = getStatelessRemoteBean(initCtx, StatelessTransactionBean.class);
+ Result result = bean.increment();
+ int count = 1;
+ Assert.assertEquals(count++, result.getValue().intValue());
+
+ targetNode = result.getNode();
+
+ undeploy(this.findDeployment(targetNode));
+
+ try {
+ result = bean.increment();
+ Assert.fail("Expected a NoSuchEJBException as transaction affinity needs to be maintained");
+ } catch (NoSuchEJBException expected) {
+ // expected as the deployment was removed from the node
+ }
+ } finally {
+ if(txn != null) txn.rollback();
+ initCtx.close();
+ }
+ }
+
+ private Incrementor getStatelessRemoteBean(InitialContext ctx, Class extends Incrementor> beanClass) {
+ return getRemoteBean(ctx, beanClass, false);
+ }
+
+ private Incrementor getRemoteBean(InitialContext ctx, Class extends Incrementor> beanClass, boolean isStateful) {
+ String lookupBeanUrl = "ejb:/" + MODULE_NAME +"/" + beanClass.getSimpleName() + "!" + Incrementor.class.getName();
+ if(isStateful) lookupBeanUrl += "?stateful";
+
+ try {
+ return (Incrementor) ctx.lookup(lookupBeanUrl);
+ } catch (NamingException ne) {
+ throw new IllegalStateException(String.format("Cannot find ejb bean '%s' while "
+ + "looking up for name '%s'", beanClass, lookupBeanUrl), ne);
+ }
+ }
+
+ private UserTransaction getUserTransaction(InitialContext ctx) {
+ String lookupUserTransaction = "txn:RemoteUserTransaction";
+ try {
+ return (UserTransaction) ctx.lookup(lookupUserTransaction);
+ } catch (NamingException ne) {
+ throw new IllegalStateException(String.format("Cannot look up '%s' at context: %s",
+ ctx, lookupUserTransaction), ne);
+ }
+ }
+
+ private InitialContext getInitialContext(InitialContextLookupType lookupType, String nodeHostName) {
+ final Hashtable jndiProperties = new Hashtable<>();
+ jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
+ if(nodeHostName.contains(":") && !nodeHostName.contains("[")) {
+ nodeHostName = "[" + nodeHostName + "]"; // escaping host and port for IPv6
+ }
+
+ String lookupProviderUrl = "";
+ switch(lookupType) {
+ case HTTP:
+ lookupProviderUrl = String.format("http://%s:%s/wildfly-services", nodeHostName, node0Port);
+ jndiProperties.put(Context.PROVIDER_URL, lookupProviderUrl);
+ // see server/configuration/application-users.properties
+ jndiProperties.put(Context.SECURITY_PRINCIPAL, "user1");
+ jndiProperties.put(Context.SECURITY_CREDENTIALS, "password1");
+ break;
+ case REMOTE_HTTP:
+ lookupProviderUrl = String.format("remote+http://%s:%s", nodeHostName, node0Port);
+ jndiProperties.put(Context.PROVIDER_URL, lookupProviderUrl);
+ break;
+ case WILDFLY_CONFIG:
+ default:
+ // leaving as it is, remote host config taken from the client's wildfly-config.xml
+ log.debugf("Not using value '%s' of nodeHostName parameter, "
+ + "wildfly-config.xml defines the URL on its own", nodeHostName);
+ }
+
+ try {
+ return new InitialContext(jndiProperties);
+ } catch (NamingException ne) {
+ throw new IllegalStateException(String.format(
+ "Cannot create InitialContext with url provider '%s'",
+ lookupProviderUrl), ne);
+ }
+ }
+}
diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionBean.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionBean.java
new file mode 100644
index 000000000000..dcfd24b15e01
--- /dev/null
+++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionBean.java
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2019, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.as.test.clustering.cluster.ejb.remote.bean;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.Remote;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.transaction.TransactionManager;
+
+import org.jboss.as.test.integration.transactions.TransactionCheckerSingleton;
+import org.jboss.as.test.integration.transactions.TxTestUtil;
+
+@Stateless
+@Remote(Incrementor.class)
+@TransactionAttribute(TransactionAttributeType.MANDATORY)
+public class StatelessTransactionBean extends IncrementorBean {
+ @EJB
+ private TransactionCheckerSingleton txnChecker;
+
+ @Resource(lookup = "java:/TransactionManager")
+ private TransactionManager tm;
+
+ @Override
+ public Result increment() {
+ TxTestUtil.addSynchronization(tm, txnChecker);
+ TxTestUtil.enlistTestXAResource(tm, txnChecker);
+ return super.increment();
+ }
+}
diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionNoResourceBean.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionNoResourceBean.java
new file mode 100644
index 000000000000..764f43492823
--- /dev/null
+++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/StatelessTransactionNoResourceBean.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2019, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.as.test.clustering.cluster.ejb.remote.bean;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.Remote;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.transaction.TransactionSynchronizationRegistry;
+
+import org.jboss.as.test.integration.transactions.TransactionCheckerSingleton;
+import org.jboss.as.test.integration.transactions.TxTestUtil;
+
+@Stateless
+@Remote(Incrementor.class)
+@TransactionAttribute(TransactionAttributeType.MANDATORY)
+public class StatelessTransactionNoResourceBean extends IncrementorBean {
+ @EJB
+ TransactionCheckerSingleton txnChecker;
+
+ @Resource
+ private TransactionSynchronizationRegistry transactionSynchronizationRegistry;
+
+ @Override
+ public Result increment() {
+ TxTestUtil.addSynchronization(transactionSynchronizationRegistry, txnChecker);
+ return super.increment();
+ }
+}
diff --git a/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TransactionTestLookupUtil.java b/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TransactionTestLookupUtil.java
index a4a363dec197..d6dfac231bf4 100644
--- a/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TransactionTestLookupUtil.java
+++ b/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TransactionTestLookupUtil.java
@@ -31,7 +31,7 @@
import org.jboss.logging.Logger;
/**
- * Util class which is used for remote lookups in transaction relatd tests.
+ * Util class which is used for remote lookups in transaction related tests.
*
* @author Ondra Chaloupka
*/
diff --git a/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TxTestUtil.java b/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TxTestUtil.java
index aa291a311402..4008d6d086a7 100644
--- a/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TxTestUtil.java
+++ b/testsuite/shared/src/main/java/org/jboss/as/test/integration/transactions/TxTestUtil.java
@@ -46,6 +46,15 @@ private TxTestUtil() {
// no instance here
}
+ public static TestXAResource enlistTestXAResource(TransactionManager tm, TransactionCheckerSingleton checker) {
+ try {
+ return enlistTestXAResource(tm.getTransaction(), checker);
+ } catch (SystemException se) {
+ throw new RuntimeException(String.format("Can't obtain transaction for transaction manager '%s' "
+ + "to enlist %s", tm, TestXAResource.class.getName()), se);
+ }
+ }
+
public static TestXAResource enlistTestXAResource(Transaction txn, TransactionCheckerSingleton checker) {
TestXAResource xaResource = new TestXAResource(checker);
try {
@@ -64,6 +73,15 @@ public static void enlistTestXAResource(Transaction txn, XAResource xaResource)
}
}
+ public static void addSynchronization(TransactionManager tm, TransactionCheckerSingletonRemote checker) {
+ try {
+ addSynchronization(tm.getTransaction(), checker);
+ } catch (SystemException se) {
+ throw new RuntimeException(String.format("Can't obtain transaction for transaction manager '%s' "
+ + "to enlist add test synchronization '%s'"), se);
+ }
+ }
+
public static void addSynchronization(Transaction txn, TransactionCheckerSingletonRemote checker) {
TestSynchronization synchro = new TestSynchronization(checker);
try {