/**
*
* Copyright 2004 Protique Ltd
*
* Licensed 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.activemq.transport;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.UnsupportedWireFormatException;
import org.activemq.broker.BrokerConnector;
import org.activemq.io.WireFormat;
import org.activemq.message.Packet;
import org.activemq.message.PacketListener;
import org.activemq.message.Receipt;
import org.activemq.message.ReceiptHolder;
import org.activemq.message.WireFormatInfo;
import org.activemq.util.ExecutorHelper;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
import EDU.oswego.cs.dl.util.concurrent.Executor;
/**
* Some basic functionality, common across most transport implementations of channels
*
* @version $Revision: 1.1.1.1 $
*/
public abstract class TransportChannelSupport implements TransportChannel {
private static final Log log = LogFactory.getLog(TransportChannelSupport.class);
private CopyOnWriteArrayList listeners = new CopyOnWriteArrayList();
private ConcurrentHashMap requestMap = new ConcurrentHashMap();
private PacketListener packetListener;
private ExceptionListener exceptionListener;
private String clientID;
private TransportChannelListener transportChannelListener;
private long lastReceiptTimstamp = 0;
private boolean serverSide;
protected boolean pendingStop = false;
protected boolean transportConnected = true;
protected WireFormat currentWireFormat;
protected boolean cachingEnabled = false;
protected boolean noDelay = false;
protected boolean usedInternally = false; //denotes if transport is used by an internal Connection
protected TransportChannelSupport(){
}
protected TransportChannelSupport(WireFormat wf){
this.currentWireFormat = wf;
}
/**
* Give the TransportChannel a hint it's about to stop
*
* @param pendingStop
*/
public void setPendingStop(boolean pendingStop) {
this.pendingStop = pendingStop;
}
/**
* @return true if the channel is about to stop
*/
public boolean isPendingStop() {
return pendingStop;
}
/**
* set the wire format to be used by this channel
* @param wireformat
*/
public void setWireFormat(WireFormat wireformat){
currentWireFormat = wireformat;
}
/**
* Get the current wireformat used by this channel
* @return the current wire format - or null if not set
*/
public WireFormat getWireFormat(){
return currentWireFormat;
}
/**
* close the channel
*/
public void stop() {
transportConnected = false;
Map map = new HashMap(this.requestMap);
for (Iterator i = map.values().iterator();i.hasNext();) {
ReceiptHolder rh = (ReceiptHolder) i.next();
rh.close();
}
map.clear();
requestMap.clear();
if (transportChannelListener != null) {
transportChannelListener.removeClient(this);
}
exceptionListener = null;
packetListener = null;
}
/**
* synchronously send a Packet
*
* @param packet
* @return a Receipt
* @throws JMSException
*/
public Receipt send(Packet packet) throws JMSException {
return send(packet, 0);
}
/**
* Synchronously send a Packet
*
* @param packet packet to send
* @param timeout amount of time to wait for a receipt
* @return the Receipt
* @throws JMSException
*/
public Receipt send(Packet packet, int timeout) throws JMSException {
ReceiptHolder rh = asyncSendWithReceipt(packet);
Receipt result = rh.getReceipt(timeout);
return result;
}
/**
* Asynchronously send a Packet with receipt.
*
* @param packet the packet to send
* @return a ReceiptHolder for the packet
* @throws JMSException
*/
public ReceiptHolder asyncSendWithReceipt(Packet packet) throws JMSException {
ReceiptHolder rh = new ReceiptHolder();
requestMap.put(new Short(packet.getId()), rh);
Packet response = doAsyncSend(packet);
if (response != null && response instanceof Receipt){
rh.setReceipt((Receipt)response);
}
return rh;
}
// Properties
//-------------------------------------------------------------------------
/**
* @return the transportChannelListener
*/
public TransportChannelListener getTransportChannelListener() {
return transportChannelListener;
}
/**
* @param transportChannelListener
*/
public void setTransportChannelListener(TransportChannelListener transportChannelListener) {
this.transportChannelListener = transportChannelListener;
}
/**
* Add a listener for changes in a channels status
*
* @param listener
*/
public void addTransportStatusEventListener(TransportStatusEventListener listener) {
listeners.add(listener);
}
/**
* Remove a listener for changes in a channels status
*
* @param listener
*/
public void removeTransportStatusEventListener(TransportStatusEventListener listener) {
listeners.remove(listener);
}
/**
* @return the clientID
*/
public String getClientID() {
return clientID;
}
/**
* @param clientID set the clientID
*/
public void setClientID(String clientID) {
this.clientID = clientID;
}
/**
* @return the exception listener
*/
public ExceptionListener getExceptionListener() {
return exceptionListener;
}
/**
* @return the packet listener
*/
public PacketListener getPacketListener() {
return packetListener;
}
/**
* Set a listener for Packets
*
* @param l
*/
public void setPacketListener(PacketListener l) {
this.packetListener = l;
}
/**
* Set an exception listener to listen for asynchronously generated exceptions
*
* @param listener
*/
public void setExceptionListener(ExceptionListener listener) {
this.exceptionListener = listener;
}
/**
* @return true if server side
*/
public boolean isServerSide() {
return serverSide;
}
/**
* @param serverSide
*/
public void setServerSide(boolean serverSide) {
this.serverSide = serverSide;
}
/**
* @return true if the transport channel is active,
* this value will be false through reconnecting
*/
public boolean isTransportConnected(){
return transportConnected;
}
protected void setTransportConnected(boolean value){
transportConnected = value;
}
/**
* Some transports rely on an embedded broker (beer based protocols)
* @return true if an embedded broker required
*/
public boolean requiresEmbeddedBroker(){
return false;
}
/**
* Some transports that rely on an embedded broker need to
* create the connector used by the broker
* @return the BrokerConnector or null if not applicable
* @throws JMSException
*/
public BrokerConnector getEmbeddedBrokerConnector() throws JMSException{
return null;
}
/**
* @return true if this transport is multicast based (i.e. broadcasts to multiple nodes)
*/
public boolean isMulticast(){
return false;
}
/**
* Can this wireformat process packets of this version
* @param version the version number to test
* @return true if can accept the version
*/
public boolean canProcessWireFormatVersion(int version){
return true;
}
public long getLastReceiptTimestamp() {
return lastReceiptTimstamp;
}
/**
* @return Returns the usedInternally.
*/
public boolean isUsedInternally() {
return usedInternally;
}
/**
* @param usedInternally The usedInternally to set.
*/
public void setUsedInternally(boolean usedInternally) {
this.usedInternally = usedInternally;
}
/**
* Does the transport support wire format version info
* @return
*/
public boolean doesSupportWireFormatVersioning(){
return true;
}
/**
* @return the current version of this wire format
*/
public int getCurrentWireFormatVersion(){
return -1;
}
/**
* some transports/wire formats will implement their own fragementation
* @return true unless a transport/wire format supports it's own fragmentation
*/
public boolean doesSupportMessageFragmentation(){
return getWireFormat() != null && getWireFormat().doesSupportMessageFragmentation();
}
/**
* Some transports/wireformats will not be able to understand compressed messages
* @return true unless a transport/wire format cannot understand compression
*/
public boolean doesSupportMessageCompression(){
return getWireFormat() != null && getWireFormat().doesSupportMessageCompression();
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* consume a packet from the channel
*
* @param packet
* @throws UnsupportedWireFormatException
*/
protected void doConsumePacket(Packet packet) {
doConsumePacket(packet, packetListener);
}
protected void doConsumePacket(Packet packet, PacketListener listener) {
if (!doHandleReceipt(packet) && !doHandleWireFormat(packet)) {
if (listener != null) {
listener.consume(packet);
}
else {
log.warn("No packet listener set to receive packets");
}
}
}
protected boolean doHandleReceipt(Packet packet) {
boolean result = false;
if (packet != null) {
if (packet.isReceipt()) {
lastReceiptTimstamp = System.currentTimeMillis();
result = true;
Receipt receipt = (Receipt) packet;
ReceiptHolder rh = (ReceiptHolder) requestMap.remove(new Short(receipt.getCorrelationId()));
if (rh != null) {
rh.setReceipt(receipt);
}
else {
log.warn("No Packet found to match Receipt correlationId: " + receipt.getCorrelationId());
}
}
}
return result;
}
protected boolean doHandleWireFormat(Packet packet) {
boolean handled = false;
if (packet.getPacketType() == Packet.WIRE_FORMAT_INFO) {
handled = true;
WireFormatInfo info = (WireFormatInfo) packet;
if (!canProcessWireFormatVersion(info.getVersion())) {
setPendingStop(true);
String errorStr = "Cannot process wire format of version: " + info.getVersion();
TransportStatusEvent event = new TransportStatusEvent();
event.setChannelStatus(TransportStatusEvent.FAILED);
fireStatusEvent(event);
onAsyncException(new UnsupportedWireFormatException(errorStr));
stop();
}
else {
if (log.isDebugEnabled()) {
log.debug(this + " using wire format version: " + info.getVersion());
}
}
}
return handled;
}
/**
* send a Packet to the raw underlying transport This method is here to allow specific implementations to override
* this method
*
* @param packet
* @return a response or null
* @throws JMSException
*/
protected Packet doAsyncSend(Packet packet) throws JMSException {
asyncSend(packet);
return null;
}
/**
* Handles an exception thrown while performing async dispatch of messages
*
* @param e
*/
protected void onAsyncException(JMSException e) {
if (exceptionListener != null) {
transportConnected = false;
exceptionListener.onException(e);
}
else {
log.warn("Caught exception dispatching message and no ExceptionListener registered: " + e, e);
}
}
/**
* Fire status event to any status event listeners
*
* @param remoteURI
* @param status
*/
protected void fireStatusEvent(URI remoteURI, int status) {
TransportStatusEvent event = new TransportStatusEvent();
event.setChannelStatus(status);
event.setRemoteURI(remoteURI);
fireStatusEvent(event);
}
/**
* Fire status event to any status event listeners
*
* @param event
*/
protected void fireStatusEvent(TransportStatusEvent event) {
if (event != null) {
for (Iterator i = listeners.iterator();i.hasNext();) {
TransportStatusEventListener l = (TransportStatusEventListener) i.next();
l.statusChanged(event);
}
}
}
/**
* A helper method to stop the execution of an executor
*
* @param executor the executor or null if one is not created yet
* @throws InterruptedException
* @throws JMSException
*/
protected void stopExecutor(Executor executor) throws InterruptedException, JMSException {
ExecutorHelper.stopExecutor(executor);
}
/**
* @return Returns the cachingEnabled.
*/
public boolean isCachingEnabled() {
return cachingEnabled;
}
/**
* @param cachingEnabled The cachingEnabled to set.
*/
public void setCachingEnabled(boolean cachingEnabled) {
this.cachingEnabled = cachingEnabled;
}
/**
* Inform Transport to send messages as quickly
* as possible - for Tcp - this means disabling Nagles,
* which on OSX may provide better performance for sync
* sends
* @return Returns the noDelay.
*/
public boolean isNoDelay() {
return noDelay;
}
/**
* @param noDelay The noDelay to set.
*/
public void setNoDelay(boolean noDelay) {
this.noDelay = noDelay;
}
}