/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.service.extensions.xa.recovery; import javax.transaction.xa.XAResource; import java.util.HashMap; import java.util.Map; import org.apache.activemq.artemis.api.core.TransportConfiguration; /** * A XAResourceRecovery instance that can be used to recover any JMS provider. * <p> * In reality only recover, rollback and commit will be called but we still need to be implement all * methods just in case. * <p> * To enable this add the following to the jbossts-properties file * <pre> * <property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.ACTIVEMQ1" * value="org.apache.activemq.artemis.jms.server.recovery.ActiveMQXAResourceRecovery;org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory"/> * </pre> * <p> * you'll need something like this if the ActiveMQ Artemis Server is remote * <pre> * <property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.ACTIVEMQ2" * value="org.apache.activemq.artemis.jms.server.recovery.ActiveMQXAResourceRecovery;org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory,guest,guest,host=localhost,port=61616"/> * </pre> * <p> * you'll need something like this if the ActiveMQ Artemis Server is remote and has failover configured * <pre> * <property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.ACTIVEMQ2" * value="org.apache.activemq.artemis.jms.server.recovery.ActiveMQXAResourceRecovery;org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory,guest,guest,host=localhost,port=61616;org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory,guest,guest,host=localhost2,port=61617"/> * </pre> */ public class ActiveMQXAResourceRecovery { private final boolean trace = ActiveMQXARecoveryLogger.LOGGER.isTraceEnabled(); private boolean hasMore; private ActiveMQXAResourceWrapper res; public ActiveMQXAResourceRecovery() { if (trace) { ActiveMQXARecoveryLogger.LOGGER.trace("Constructing ActiveMQXAResourceRecovery"); } } public boolean initialise(final String config) { if (ActiveMQXARecoveryLogger.LOGGER.isTraceEnabled()) { ActiveMQXARecoveryLogger.LOGGER.trace(this + " initialise: " + config); } String[] configs = config.split(";"); XARecoveryConfig[] xaRecoveryConfigs = new XARecoveryConfig[configs.length]; for (int i = 0, configsLength = configs.length; i < configsLength; i++) { String s = configs[i]; ConfigParser parser = new ConfigParser(s); String connectorFactoryClassName = parser.getConnectorFactoryClassName(); Map<String, Object> connectorParams = parser.getConnectorParameters(); String username = parser.getUsername(); String password = parser.getPassword(); TransportConfiguration transportConfiguration = new TransportConfiguration(connectorFactoryClassName, connectorParams); xaRecoveryConfigs[i] = new XARecoveryConfig(false, new TransportConfiguration[]{transportConfiguration}, username, password, null, null); } res = new ActiveMQXAResourceWrapper(xaRecoveryConfigs); if (ActiveMQXARecoveryLogger.LOGGER.isTraceEnabled()) { ActiveMQXARecoveryLogger.LOGGER.trace(this + " initialised"); } return true; } public boolean hasMoreResources() { if (ActiveMQXARecoveryLogger.LOGGER.isTraceEnabled()) { ActiveMQXARecoveryLogger.LOGGER.trace(this + " hasMoreResources"); } /* * The way hasMoreResources is supposed to work is as follows: * For each "sweep" the recovery manager will call hasMoreResources, then if it returns * true it will call getXAResource. * It will repeat that until hasMoreResources returns false. * Then the sweep is over. * For the next sweep hasMoreResources should return true, etc. * * In our case where we only need to return one XAResource per sweep, * hasMoreResources should basically alternate between true and false. * * */ hasMore = !hasMore; return hasMore; } public XAResource getXAResource() { if (ActiveMQXARecoveryLogger.LOGGER.isTraceEnabled()) { ActiveMQXARecoveryLogger.LOGGER.trace(this + " getXAResource"); } return res; } public XAResource[] getXAResources() { return new XAResource[]{res}; } @Override protected void finalize() { res.close(); } public static class ConfigParser { private final String connectorFactoryClassName; private final Map<String, Object> connectorParameters; private String username; private String password; public ConfigParser(final String config) { if (config == null || config.length() == 0) { throw new IllegalArgumentException("Must specify provider connector factory class name in config"); } String[] strings = config.split(","); // First (mandatory) param is the connector factory class name if (strings.length < 1) { throw new IllegalArgumentException("Must specify provider connector factory class name in config"); } connectorFactoryClassName = strings[0].trim(); // Next two (optional) parameters are the username and password to use for creating the session for recovery if (strings.length >= 2) { username = strings[1].trim(); if (username.length() == 0) { username = null; } if (strings.length == 2) { throw new IllegalArgumentException("If username is specified, password must be specified too"); } password = strings[2].trim(); if (password.length() == 0) { password = null; } } // other tokens are for connector configurations connectorParameters = new HashMap<>(); if (strings.length >= 3) { for (int i = 3; i < strings.length; i++) { String[] str = strings[i].split("="); if (str.length == 2) { connectorParameters.put(str[0].trim(), str[1].trim()); } } } } public String getConnectorFactoryClassName() { return connectorFactoryClassName; } public Map<String, Object> getConnectorParameters() { return connectorParameters; } public String getUsername() { return username; } public String getPassword() { return password; } } }