/**
* 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.broker.ft;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.activemq.broker.Connection;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.ConsumerBrokerExchange;
import org.apache.activemq.broker.InsertableMutableBrokerFilter;
import org.apache.activemq.broker.MutableBrokerFilter;
import org.apache.activemq.broker.ProducerBrokerExchange;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.Command;
import org.apache.activemq.command.ConnectionControl;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.ExceptionResponse;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.MessageDispatchNotification;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.RemoveInfo;
import org.apache.activemq.command.RemoveSubscriptionInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.command.SessionInfo;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.command.TransactionInfo;
import org.apache.activemq.transport.MutexTransport;
import org.apache.activemq.transport.ResponseCorrelator;
import org.apache.activemq.transport.Transport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The Message Broker which passes messages to a slave
*
* @version $Revision: 1.8 $
*/
public class MasterBroker extends InsertableMutableBrokerFilter {
private static final Log LOG = LogFactory.getLog(MasterBroker.class);
private Transport slave;
private AtomicBoolean started = new AtomicBoolean(false);
/**
* Constructor
*
* @param parent
* @param transport
*/
public MasterBroker(MutableBrokerFilter parent, Transport transport) {
super(parent);
this.slave = transport;
this.slave = new MutexTransport(slave);
this.slave = new ResponseCorrelator(slave);
this.slave.setTransportListener(transport.getTransportListener());
}
/**
* start processing this broker
*/
public void startProcessing() {
started.set(true);
try {
Connection[] connections = getClients();
ConnectionControl command = new ConnectionControl();
command.setFaultTolerant(true);
if (connections != null) {
for (int i = 0; i < connections.length; i++) {
if (connections[i].isActive() && connections[i].isManageable()) {
connections[i].dispatchAsync(command);
}
}
}
} catch (Exception e) {
LOG.error("Failed to get Connections", e);
}
}
/**
* stop the broker
*
* @throws Exception
*/
public void stop() throws Exception {
super.stop();
stopProcessing();
}
/**
* stop processing this broker
*/
public void stopProcessing() {
if (started.compareAndSet(true, false)) {
remove();
}
}
/**
* A client is establishing a connection with the broker.
*
* @param context
* @param info
* @throws Exception
*/
public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
super.addConnection(context, info);
sendAsyncToSlave(info);
}
/**
* A client is disconnecting from the broker.
*
* @param context the environment the operation is being executed under.
* @param info
* @param error null if the client requested the disconnect or the error
* that caused the client to disconnect.
* @throws Exception
*/
public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
super.removeConnection(context, info, error);
sendAsyncToSlave(new RemoveInfo(info.getConnectionId()));
}
/**
* Adds a session.
*
* @param context
* @param info
* @throws Exception
*/
public void addSession(ConnectionContext context, SessionInfo info) throws Exception {
super.addSession(context, info);
sendAsyncToSlave(info);
}
/**
* Removes a session.
*
* @param context
* @param info
* @throws Exception
*/
public void removeSession(ConnectionContext context, SessionInfo info) throws Exception {
super.removeSession(context, info);
sendAsyncToSlave(new RemoveInfo(info.getSessionId()));
}
/**
* Adds a producer.
*
* @param context the enviorment the operation is being executed under.
* @param info
* @throws Exception
*/
public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
super.addProducer(context, info);
sendAsyncToSlave(info);
}
/**
* Removes a producer.
*
* @param context the enviorment the operation is being executed under.
* @param info
* @throws Exception
*/
public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception {
super.removeProducer(context, info);
sendAsyncToSlave(new RemoveInfo(info.getProducerId()));
}
/**
* add a consumer
*
* @param context
* @param info
* @return the assocated subscription
* @throws Exception
*/
public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
sendAsyncToSlave(info);
Subscription answer = super.addConsumer(context, info);
return answer;
}
/**
* remove a subscription
*
* @param context
* @param info
* @throws Exception
*/
public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception {
super.removeSubscription(context, info);
sendAsyncToSlave(info);
}
/**
* begin a transaction
*
* @param context
* @param xid
* @throws Exception
*/
public void beginTransaction(ConnectionContext context, TransactionId xid) throws Exception {
TransactionInfo info = new TransactionInfo(context.getConnectionId(), xid, TransactionInfo.BEGIN);
sendAsyncToSlave(info);
super.beginTransaction(context, xid);
}
/**
* Prepares a transaction. Only valid for xa transactions.
*
* @param context
* @param xid
* @return the state
* @throws Exception
*/
public int prepareTransaction(ConnectionContext context, TransactionId xid) throws Exception {
TransactionInfo info = new TransactionInfo(context.getConnectionId(), xid, TransactionInfo.PREPARE);
sendAsyncToSlave(info);
int result = super.prepareTransaction(context, xid);
return result;
}
/**
* Rollsback a transaction.
*
* @param context
* @param xid
* @throws Exception
*/
public void rollbackTransaction(ConnectionContext context, TransactionId xid) throws Exception {
TransactionInfo info = new TransactionInfo(context.getConnectionId(), xid, TransactionInfo.ROLLBACK);
sendAsyncToSlave(info);
super.rollbackTransaction(context, xid);
}
/**
* Commits a transaction.
*
* @param context
* @param xid
* @param onePhase
* @throws Exception
*/
public void commitTransaction(ConnectionContext context, TransactionId xid, boolean onePhase) throws Exception {
TransactionInfo info = new TransactionInfo(context.getConnectionId(), xid, TransactionInfo.COMMIT_ONE_PHASE);
sendSyncToSlave(info);
super.commitTransaction(context, xid, onePhase);
}
/**
* Forgets a transaction.
*
* @param context
* @param xid
* @throws Exception
*/
public void forgetTransaction(ConnectionContext context, TransactionId xid) throws Exception {
TransactionInfo info = new TransactionInfo(context.getConnectionId(), xid, TransactionInfo.FORGET);
sendAsyncToSlave(info);
super.forgetTransaction(context, xid);
}
/**
* Notifiy the Broker that a dispatch has happened
*
* @param messageDispatch
*/
public void postProcessDispatch(MessageDispatch messageDispatch) {
MessageDispatchNotification mdn = new MessageDispatchNotification();
mdn.setConsumerId(messageDispatch.getConsumerId());
mdn.setDeliverySequenceId(messageDispatch.getDeliverySequenceId());
mdn.setDestination(messageDispatch.getDestination());
if (messageDispatch.getMessage() != null) {
Message msg = messageDispatch.getMessage();
mdn.setMessageId(msg.getMessageId());
sendAsyncToSlave(mdn);
}
super.postProcessDispatch(messageDispatch);
}
/**
* @param context
* @param message
* @throws Exception
*/
public void send(ProducerBrokerExchange producerExchange, Message message) throws Exception {
/**
* A message can be dispatched before the super.send() method returns so -
* here the order is switched to avoid problems on the slave with
* receiving acks for messages not received yey
*/
sendToSlave(message);
super.send(producerExchange, message);
}
/**
* @param context
* @param ack
* @throws Exception
*/
public void acknowledge(ConsumerBrokerExchange consumerExchange, MessageAck ack) throws Exception {
sendToSlave(ack);
super.acknowledge(consumerExchange, ack);
}
public boolean isFaultTolerantConfiguration() {
return true;
}
protected void sendToSlave(Message message) {
if (message.isResponseRequired()) {
sendSyncToSlave(message);
} else {
sendAsyncToSlave(message);
}
}
protected void sendToSlave(MessageAck ack) {
if (ack.isResponseRequired()) {
sendAsyncToSlave(ack);
} else {
sendSyncToSlave(ack);
}
}
protected void sendAsyncToSlave(Command command) {
try {
slave.oneway(command);
} catch (Throwable e) {
LOG.error("Slave Failed", e);
stopProcessing();
}
}
protected void sendSyncToSlave(Command command) {
try {
Response response = (Response)slave.request(command);
if (response.isException()) {
ExceptionResponse er = (ExceptionResponse)response;
LOG.error("Slave Failed", er.getException());
}
} catch (Throwable e) {
LOG.error("Slave Failed", e);
}
}
}