/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.transport.multiplex;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import javax.net.SocketFactory;
import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.marshal.serializable.SerializableMarshaller;
import org.jboss.remoting.transport.multiplex.MultiplexServerInvoker.SocketGroupInfo;
import org.jboss.remoting.transport.multiplex.utility.AddressPair;
import org.jboss.remoting.transport.socket.ClientSocketWrapper;
import org.jboss.remoting.transport.socket.SocketClientInvoker;
/**
* See javadoc for <code>MultiplexServerInvoker</code>.
*
* @author <a href="mailto:tom.elrod@jboss.com">Tom Elrod</a>
*/
public class MultiplexClientInvoker extends SocketClientInvoker implements MultiplexInvokerConstants
{
private InetAddress connectAddress;
private String connectHost;
private int connectPort;
private InetSocketAddress connectSocketAddress;
private InetSocketAddress bindSocketAddress;
private String socketGroupId;
private MultiplexServerInvoker.SocketGroupInfo socketGroupInfo;
private AddressPair addressPair;
private boolean readyToRun;
protected String clientSocketClassName = ClientSocketWrapper.class.getName();
private Constructor clientSocketConstructor = null;
/**
*
* Create a new MultiplexClientInvoker.
*
* @param locator
*/
public MultiplexClientInvoker(InvokerLocator locator) throws IOException
{
super(locator);
}
/**
*
* Create a new MultiplexClientInvoker.
*
* @param locator
*/
public MultiplexClientInvoker(InvokerLocator locator, Map configuration) throws IOException
{
super(locator, configuration);
}
/**
* FIXME Comment this
*
* @throws Exception
*/
protected void setup() throws Exception
{
log.debug("configuring MultiplexClientInvoker for: " + locator);
super.setup();
this.connectAddress = InetAddress.getByName(locator.getHost());
this.connectHost = connectAddress.getHostName();
this.connectPort = locator.getPort();
this.connectSocketAddress = new InetSocketAddress(connectAddress, connectPort);
Map parameters = configuration;
if (parameters != null)
{
configureSocketGroupParameters(parameters);
}
}
/**
* FIXME Comment this
*
*
*/
public void finishStart() throws IOException
{
if (socketGroupInfo != null && bindSocketAddress == null)
{
InetAddress bindAddress = socketGroupInfo.getBindAddress();
int bindPort = socketGroupInfo.getBindPort();
bindSocketAddress = new InetSocketAddress(bindAddress, bindPort);
}
if (socketGroupInfo != null && addressPair == null)
{
String bindHost = socketGroupInfo.getBindAddress().getHostName();
int bindPort = socketGroupInfo.getBindPort();
addressPair = new AddressPair(connectHost, connectPort, bindHost, bindPort);
}
readyToRun = true;
}
/**
* FIXME Comment this
*
* @param parameters
* @throws IOException
*/
protected void configureSocketGroupParameters(Map parameters) throws IOException
{
String bindHost;
String bindPortString;
int bindPort = -1;
InetAddress bindAddress = null;
socketGroupId = (String) parameters.get(CLIENT_MULTIPLEX_ID_KEY);
log.debug("socketGroupId: " + socketGroupId);
synchronized (MultiplexServerInvoker.SocketGroupInfo.class)
{
if (socketGroupId != null)
socketGroupInfo = (SocketGroupInfo) MultiplexServerInvoker.getSocketGroupMap().get(socketGroupId);
if (socketGroupInfo != null && socketGroupInfo.getServerInvoker() != null)
{
log.debug("client rule 1");
// If we get here, it's because a MultiplexServerInvoker created a SocketGroupInfo with matching
// group id. We want to make sure that it didn't get a connect address or connect port different
// than the ones passed in through the parameters map.
InetAddress socketGroupConnectAddress = socketGroupInfo.getConnectAddress();
int socketGroupConnectPort = socketGroupInfo.getConnectPort();
if (socketGroupConnectAddress != null && !socketGroupConnectAddress.equals(connectAddress))
{
String message = "socket group connect address (" + socketGroupConnectAddress +
") does not match connect address (" + connectAddress + ")";
log.error(message);
throw new IOException(message);
}
if (socketGroupConnectPort > 0 && socketGroupConnectPort != connectPort)
{
String message = "socket group connect port (" + socketGroupConnectPort +
") does not match connect port (" + connectPort + ")";
log.error(message);
throw new IOException(message);
}
bindAddress = socketGroupInfo.getBindAddress();
bindPort = socketGroupInfo.getBindPort();
bindSocketAddress = new InetSocketAddress(bindAddress, bindPort);
socketGroupInfo.setConnectAddress(connectAddress);
socketGroupInfo.setConnectPort(connectPort);
socketGroupInfo.addClientInvoker(this);
if (socketGroupInfo.getPrimingSocket() == null)
{
String connectHost = connectAddress.getHostName();
MultiplexServerInvoker.createPrimingSocket(socketGroupInfo, connectHost, connectPort,
bindAddress, bindPort,
getSocketFactory(), timeout);
}
// We got socketGroupInfo by socketGroupId. Make sure it is also stored by AddressPair.
bindHost = bindAddress.getHostName();
addressPair = new AddressPair(connectHost, connectPort, bindHost, bindPort);
MultiplexServerInvoker.getAddressPairMap().put(addressPair, socketGroupInfo);
MultiplexServerInvoker serverInvoker = socketGroupInfo.getServerInvoker();
if (serverInvoker != null)
serverInvoker.finishStart();
finishStart();
return;
}
bindHost = (String) parameters.get(MULTIPLEX_BIND_HOST_KEY);
bindPortString = (String) parameters.get(MULTIPLEX_BIND_PORT_KEY);
if (bindHost != null && bindPortString == null)
{
bindPortString = "0";
}
if (bindHost == null && bindPortString != null)
{
bindHost = "localhost";
}
if (bindHost != null)
{
log.debug("client rule 2");
try
{
bindPort = Integer.parseInt(bindPortString);
}
catch (NumberFormatException e)
{
throw new IOException("number format error for bindPort: " + bindPortString);
}
bindSocketAddress = new InetSocketAddress(bindHost, bindPort);
addressPair = new AddressPair(connectHost, connectPort, bindHost, bindPort);
socketGroupInfo = (SocketGroupInfo) MultiplexServerInvoker.getAddressPairMap().get(addressPair);
// If socketGroupInfo exists, it's because it was created, along with a priming socket, by a
// MultiplexServerInvoker.
if (socketGroupInfo != null)
{
socketGroupInfo.setConnectAddress(connectAddress);
socketGroupInfo.setConnectPort(connectPort);
socketGroupInfo.addClientInvoker(this);
// We got socketGroupInfo by AddressPair. Make sure it is stored by socketGroupId, if we have one.
if (socketGroupId != null)
{
String socketGroupSocketGroupId = socketGroupInfo.getSocketGroupId();
if (socketGroupSocketGroupId != null && !socketGroupSocketGroupId.equals(socketGroupId))
{
String message = "socket group multiplexId (" + socketGroupSocketGroupId +
") does not match multiplexId (" + socketGroupId + ")";
log.error(message);
throw new IOException(message);
}
if (socketGroupSocketGroupId == null)
{
socketGroupInfo.setSocketGroupId(socketGroupId);
MultiplexServerInvoker.getSocketGroupMap().put(socketGroupId, socketGroupInfo);
}
}
finishStart();
return;
}
socketGroupInfo = new SocketGroupInfo();
socketGroupInfo.setConnectAddress(connectAddress);
socketGroupInfo.setConnectPort(connectPort);
socketGroupInfo.addClientInvoker(this);
// Set bindAddress and bindPort to be able to test for inconsistencies with bind address
// and bind port determined by companion MultiplexServerInvoker.
bindAddress = InetAddress.getByName(bindHost);
socketGroupInfo.setBindAddress(bindAddress);
socketGroupInfo.setBindPort(bindPort);
String connectHost = connectAddress.getHostName();
MultiplexServerInvoker.createPrimingSocket(socketGroupInfo, connectHost, connectPort,
bindAddress, bindPort, getSocketFactory(), timeout);
MultiplexServerInvoker.getAddressPairMap().put(addressPair, socketGroupInfo);
if (socketGroupId != null)
{
socketGroupInfo.setSocketGroupId(socketGroupId);
MultiplexServerInvoker.getSocketGroupMap().put(socketGroupId, socketGroupInfo);
}
finishStart();
return;
}
if (socketGroupId != null)
{
log.debug("client rule 3");
if (socketGroupInfo == null)
{
socketGroupInfo = new SocketGroupInfo();
socketGroupInfo.setSocketGroupId(socketGroupId);
socketGroupInfo.setConnectAddress(connectAddress);
socketGroupInfo.setConnectPort(connectPort);
MultiplexServerInvoker.getSocketGroupMap().put(socketGroupId, socketGroupInfo);
}
socketGroupInfo.addClientInvoker(this);
return;
}
log.debug("client rule 4");
String connectHost = connectAddress.getHostName();
MultiplexServerInvoker.createPrimingSocket(socketGroupInfo, connectHost, connectPort,
getSocketFactory(), timeout);
finishStart();
}
}
/**
* @param sessionId
* @param invocation
* @param marshaller
* @return
* @throws java.io.IOException
* @throws org.jboss.remoting.ConnectionFailedException
*
*/
protected Object transport(String sessionId, Object invocation, Map metadata, Marshaller marshaller, UnMarshaller unmarshaller)
throws IOException, ConnectionFailedException, ClassNotFoundException
{
log.debug("entering transport()");
if (!readyToRun)
throw new IOException("connection to server has not been made");
return super.transport(sessionId, invocation, metadata, marshaller, unmarshaller);
}
/**
* subclasses must implement this method to provide a hook to disconnect from the remote server, if this applies
* to the specific transport. However, in some transport implementations, this may not make must difference since
* the connection is not persistent among invocations, such as SOAP. In these cases, the method should
* silently return without any processing.
*/
protected void handleDisconnect()
{
//TODO: -TME Should this be a no op or need to pool?
log.debug("entering handleDisconnect()");
super.handleDisconnect();
synchronized (MultiplexServerInvoker.SocketGroupInfo.class)
{
if (socketGroupInfo != null)
{
socketGroupInfo.removeClientInvoker(this);
// MultiplexingManager manager = socketGroupInfo.getManager();
// if (manager != null)
// {
// try
// {
// socketGroupInfo.getManager().decrementReferences();
// }
// catch (IOException e)
// {
// log.error("error decrementing reference to MultiplexingManager");
// }
// }
if (socketGroupInfo.getClientInvokers().isEmpty() && socketGroupInfo.getServerInvoker() == null)
{
log.debug("invoker group shutting down: " + socketGroupInfo.getSocketGroupId());
if (socketGroupInfo.getPrimingSocket() != null)
{
log.debug("MultiplexClientInvoker: closing bind priming socket");
try
{
socketGroupInfo.getPrimingSocket().close();
}
catch (IOException e)
{
log.error("Error closing bind priming socket during cleanup upon stopping", e);
}
}
socketGroupId = socketGroupInfo.getSocketGroupId();
if (socketGroupId != null)
{
MultiplexServerInvoker.getSocketGroupMap().remove(socketGroupId);
}
// addressPair is set in finishStart().
if (addressPair != null)
{
MultiplexServerInvoker.getAddressPairMap().remove(addressPair);
}
}
socketGroupInfo = null; // Prevent from occurring a second time in Finalizer thread.
}
}
}
/**
* @return
*/
protected InetSocketAddress getBindSocketAddress()
{
return bindSocketAddress;
}
/**
* @return
*/
protected InetSocketAddress getConnectSocketAddress()
{
return connectSocketAddress;
}
/**
* Each implementation of the remote client invoker should have
* a default data type that is uses in the case it is not specified
* in the invoker locator uri.
*
* @return
*/
protected String getDefaultDataType()
{
return SerializableMarshaller.DATATYPE;
}
/**
* Getter for property timeout
*
* @return Value of property timeout
*/
public int getTimeout()
{
return timeout;
}
protected Socket createSocket(String address, int port) throws IOException
{
log.debug("MultiplexClientInvoker.createSocket()");
VirtualSocket socket = new VirtualSocket();
socket.setSocketFactory(getSocketFactory());
socket.connect(connectSocketAddress, bindSocketAddress, timeout);
return socket;
}
}