/*
* 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;
}
}
}