/* * JBoss, Home of Professional Open Source. * Copyright 2016, 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.narayana.tomcat.jta.integration; import com.arjuna.ats.arjuna.recovery.RecoveryManager; import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.UserTransaction; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Response; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.time.LocalTime; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.logging.Logger; /** * @author <a href="mailto:gytis@redhat.com">Gytis Trikleris</a> */ @Path(TestExecutor.BASE_PATH) public class TestExecutor { public static final String BASE_PATH = "executor"; public static final String JNDI_TEST = "jndi"; public static final String RECOVERY_TEST = "recovery"; private static final Logger LOGGER = Logger.getLogger(TestExecutor.class.getSimpleName()); @GET @Path(JNDI_TEST) public Response verifyJndi() throws NamingException { LOGGER.info("Verifying JNDI"); if (getUserTransaction() == null) { return Response.serverError().entity("UserTransaction not found in JNDI").build(); } if (getTransactionManager() == null) { return Response.serverError().entity("TransactionManager not found in JNDI").build(); } if (getTransactionSynchronizationRegistry() == null) { return Response.serverError().entity("TransactionSynchronizationRegistry not found in JNDI").build(); } if (getTransactionalDataSource() == null) { return Response.serverError().entity("DataSource not found in JNDI").build(); } return Response.noContent().build(); } @GET @Path(RECOVERY_TEST) public Response verifyRecovery() throws NamingException, HeuristicRollbackException, RollbackException, HeuristicMixedException, SystemException, NotSupportedException, SQLException { LOGGER.info("Verifying recovery"); TestXAResource.reset(); createTestTable(); String testEntry = "test-entry-" + LocalTime.now(); TestXAResource testXAResource = new TestXAResource(); updateXARecoveryModule(m -> m.addXAResourceRecoveryHelper(testXAResource)); try { getTransactionManager().begin(); getTransactionManager().getTransaction().enlistResource(testXAResource); writeToTheDatabase(testEntry); try { getTransactionManager().commit(); return Response.serverError().entity("Commit failure was expected").build(); } catch (Throwable ignored) { } return getRecoveryTestResponse(testEntry); } finally { updateXARecoveryModule(m -> m.removeXAResourceRecoveryHelper(testXAResource)); } } private void updateXARecoveryModule(Consumer<XARecoveryModule> action) { RecoveryManager.manager().getModules().stream().filter(m -> m instanceof XARecoveryModule) .forEach(m -> action.accept((XARecoveryModule) m)); } private Response getRecoveryTestResponse(String testEntry) throws SQLException, NamingException { for (int i = 0; i < 3; i++) { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.interrupted(); } if (didRecoveryHappen(testEntry)) { return Response.noContent().build(); } } return Response.serverError().entity("Recovery failed").build(); } private boolean didRecoveryHappen(String entry) throws SQLException, NamingException { List<String> expectedMethods = Arrays.asList("start", "end", "prepare", "commit"); List<String> actualMethods = TestXAResource.getMethodCalls(); LOGGER.info("Verifying TestXAResource methods. Expected=" + expectedMethods + ", actual=" + actualMethods); boolean entryExists = doesEntryExist(entry); LOGGER.info("Verifying if database entry exists:" + entryExists); return expectedMethods.equals(actualMethods) && entryExists; } private boolean doesEntryExist(String entry) throws SQLException, NamingException { String query = "SELECT COUNT(*) FROM test WHERE value='" + entry + "'"; try (Connection connection = getTransactionalDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet result = statement.executeQuery(query)) { return result.next() && result.getInt(1) > 0; } catch (SQLException e) { return false; } } private void writeToTheDatabase(String entry) throws NamingException, SQLException { String query = "INSERT INTO test VALUES ('" + entry + "')"; try (Connection connection = getTransactionalDataSource().getConnection(); Statement statement = connection.createStatement()) { statement.execute(query); } } private UserTransaction getUserTransaction() throws NamingException { return InitialContext.doLookup("java:comp/UserTransaction"); } private TransactionManager getTransactionManager() throws NamingException { return InitialContext.doLookup("java:comp/env/TransactionManager"); } private TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() throws NamingException { return InitialContext.doLookup("java:comp/env/TransactionSynchronizationRegistry"); } private DataSource getTransactionalDataSource() throws NamingException { return InitialContext.doLookup("java:comp/env/transactionalDataSource"); } private void createTestTable() throws SQLException, NamingException { String query = "CREATE TABLE IF NOT EXISTS test (value VARCHAR(100))"; try (Connection connection = getTransactionalDataSource().getConnection(); Statement statement = connection.createStatement()) { statement.execute(query); } } }