/**
*
* 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.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import javax.jms.JMSException;
import org.activemq.io.WireFormat;
import org.activemq.message.Packet;
import org.activemq.transport.TransportChannelSupport;
import org.activemq.transport.TransportStatusEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
/**
* A UDP implementation of a TransportChannel
*
* @version $Revision: 1.1.1.1 $
*/
public class UdpTransportChannel extends TransportChannelSupport implements Runnable {
private static final int SOCKET_BUFFER_SIZE = 32 * 1024;
private static final int SO_TIMEOUT = 5000;
private static final Log log = LogFactory.getLog(UdpTransportChannel.class);
protected DatagramSocket socket;
protected int port;
protected InetAddress inetAddress;
private WireFormat wireFormat;
private SynchronizedBoolean closed;
private SynchronizedBoolean started;
private Thread thread; //need to change this - and use a thread pool
/**
* Construct basic helpers
*/
protected UdpTransportChannel(WireFormat wireFormat) {
this.wireFormat = wireFormat;
closed = new SynchronizedBoolean(false);
started = new SynchronizedBoolean(false);
}
public UdpTransportChannel(WireFormat wireFormat, URI remoteLocation) throws JMSException {
this(wireFormat, remoteLocation, remoteLocation.getPort());
}
public UdpTransportChannel(WireFormat wireFormat, URI remoteLocation, int port) throws JMSException {
this(wireFormat);
try {
this.port = port;
this.inetAddress = InetAddress.getByName(remoteLocation.getHost());
this.socket = createSocket(remoteLocation.getPort());
//log.info("Creating multicast socket on port: " + port + " on
// host: " + remoteLocation.getHost());
socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
connect();
// now lets update the port so that sends will go elsewhere
}
catch (Exception ioe) {
JMSException jmsEx = new JMSException("Initialization of TransportChannel failed: " + ioe);
jmsEx.setLinkedException(ioe);
throw jmsEx;
}
}
/**
* @param socket
* @throws JMSException
*/
public UdpTransportChannel(WireFormat wireFormat, DatagramSocket socket) throws JMSException {
this(wireFormat);
this.socket = socket;
this.port = socket.getPort();
this.inetAddress = socket.getInetAddress();
try {
socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
}
catch (IOException ioe) {
JMSException jmsEx = new JMSException("Initialization of TransportChannel failed");
jmsEx.setLinkedException(ioe);
throw jmsEx;
}
}
public UdpTransportChannel(WireFormat wireFormat, DatagramSocket socket, int port) throws JMSException {
this(wireFormat, socket);
this.port = port;
}
/**
* close the channel
*/
public void stop() {
if (closed.commit(false, true)) {
super.stop();
try {
socket.close();
}
catch (Exception e) {
log.trace(toString() + " now closed");
}
}
}
public void forceDisconnect() {
log.debug("Forcing disconnect");
if (socket != null && socket.isConnected()) {
socket.close();
}
setTransportConnected(false);
fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.DISCONNECTED));
}
/**
* start listeneing for events
*
* @throws JMSException if an error occurs
*/
public void start() throws JMSException {
if (started.commit(false, true)) {
thread = new Thread(this, toString());
if (isServerSide()) {
thread.setDaemon(true);
}
thread.start();
}
}
/**
* Asynchronously send a Packet
*
* @param packet
* @throws JMSException
*/
public void asyncSend(Packet packet) throws JMSException {
try {
if (log.isDebugEnabled()) {
log.debug("Sending packet: " + packet);
}
DatagramPacket dpacket = createDatagramPacket(packet);
// lets sync to avoid concurrent writes
//synchronized (lock) {
socket.send(dpacket);
//}
}
catch (IOException e) {
JMSException jmsEx = new JMSException("asyncSend failed " + e);
jmsEx.setLinkedException(e);
onAsyncException(jmsEx);
throw jmsEx;
}
}
public boolean isMulticast() {
return false;
}
/**
* reads packets from a Socket
*/
public void run() {
DatagramPacket dpacket = createDatagramPacket();
while (!closed.get()) {
try {
socket.setSoTimeout(SO_TIMEOUT);
while (!socket.isClosed()) {
socket.setSoTimeout(0);
socket.receive(dpacket);
if (dpacket.getLength() > 0) {
Packet packet = wireFormat.readPacket(getClientID(), dpacket);
if (packet != null) {
doConsumePacket(packet);
}
}
}
log.trace("The socket peer is now closed");
doClose(new IOException("Socket peer is now closed"));
}
catch (SocketTimeoutException ste) {
//continue;
}
catch (IOException e) {
doClose(e);
}
}
}
/**
* 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 wireFormat.canProcessWireFormatVersion(version);
}
/**
* @return the current version of this wire format
*/
public int getCurrentWireFormatVersion() {
return wireFormat.getCurrentWireFormatVersion();
}
/**
* @return
*/
protected DatagramPacket createDatagramPacket() {
DatagramPacket answer = new DatagramPacket(new byte[SOCKET_BUFFER_SIZE], SOCKET_BUFFER_SIZE);
if (port >= 0) {
answer.setPort(port);
}
answer.setAddress(inetAddress);
return answer;
}
protected DatagramPacket createDatagramPacket(Packet packet) throws IOException, JMSException {
/*
* if (packet instanceof ActiveMQMessage) { ActiveMQMessage message = (ActiveMQMessage) packet;
* System.out.println(">>> about to send message with clientID: " + message.getJMSClientID()); }
*/
DatagramPacket answer = wireFormat.writePacket(getClientID(), packet);
if (port >= 0) {
answer.setPort(port);
}
answer.setAddress(inetAddress);
return answer;
}
private void doClose(Exception ex) {
if (!closed.get()) {
JMSException jmsEx = new JMSException("Error reading socket: " + ex.getMessage());
jmsEx.setLinkedException(ex);
onAsyncException(jmsEx);
stop();
}
}
protected void connect() throws IOException {
//socket.connect(inetAddress, port);
}
protected DatagramSocket createSocket(int port) throws IOException {
return new DatagramSocket(port, inetAddress);
}
/**
* pretty print for object
*
* @return String representation of this object
*/
public String toString() {
return "UdpTransportChannel: " + socket;
}
}