/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.connectivity;
import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
import org.codehaus.aspectwerkz.util.UuidGenerator;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Map;
import java.util.WeakHashMap;
/**
* This class provides a general remote proxy. It uses the Dynamic Proxy mechanism that was introduced with JDK 1.3.
* <p/>The client proxy sends all requests to a server via a socket connection. The server returns results in the same
* way. Every object that is transferred (i.e. result of method invocation) has to support the Serializable interface.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
*/
public class RemoteProxy implements InvocationHandler, Serializable {
/**
* The serial version uid for the class.
*
* @TODO: recalculate
*/
private static final long serialVersionUID = 1L;
/**
* All the instances that have been wrapped by a proxy. Maps each instance to its handle.
*/
private transient static Map s_instances = new WeakHashMap();
/**
* The server host address.
*/
private final String m_address;
/**
* The server port.
*/
private final int m_port;
/**
* The handle to the instance wrapped by this proxy.
*/
private String m_handle = null;
/**
* The interface class for the wrapped instance.
*/
private Class[] m_targetInterfaces = null;
/**
* The names of all the interfaces for the wrapped instance.
*/
private String[] m_targetInterfaceNames = null;
/**
* The class name for the wrapped instance.
*/
private String m_targetImplName = null;
/**
* The socket.
*/
private transient Socket m_socket;
/**
* The input stream.
*/
private transient ObjectInputStream m_in;
/**
* The output stream.
*/
private transient ObjectOutputStream m_out;
/**
* The class loader to use.
*/
private transient ClassLoader m_loader;
/**
* The client context.
*/
private transient Object m_context = null;
/**
* The dynamic proxy instance to the wrapped instance.
*/
private transient Object m_proxy = null;
/**
* Creates a new proxy based on the interface and class names passes to it. For client-side use. This method is
* never called directly.
*
* @param interfaces the class name of the interface for the object to create the proxy for
* @param impl the class name of the the object to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @param context the context carrying the users principal and credentials
* @param loader the class loader to use
*/
private RemoteProxy(final String[] interfaces,
final String impl,
final String address,
final int port,
final Object context,
final ClassLoader loader) {
if ((interfaces == null) || (interfaces.length == 0)) {
throw new IllegalArgumentException("at least one interface must be specified");
}
if (impl == null) {
throw new IllegalArgumentException("implementation class name can not be null");
}
if (address == null) {
throw new IllegalArgumentException("address can not be null");
}
if (port < 0) {
throw new IllegalArgumentException("port not valid");
}
m_targetInterfaceNames = interfaces;
m_targetImplName = impl;
m_address = address;
m_port = port;
m_context = context;
m_loader = loader;
}
/**
* Creates a new proxy based on the instance passed to it. For server-side use. This method is never called
* directly.
*
* @param targetInstance target instance to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
*/
private RemoteProxy(final Object targetInstance, final String address, final int port) {
if (targetInstance == null) {
throw new IllegalArgumentException("target instance can not be null");
}
if (address == null) {
throw new IllegalArgumentException("address can not be null");
}
if (port < 0) {
throw new IllegalArgumentException("port not valid");
}
m_targetInterfaces = targetInstance.getClass().getInterfaces();
m_address = address;
m_port = port;
m_handle = wrapInstance(targetInstance);
}
/**
* Creates a new proxy to a class. To be used on the client side to create a new proxy to an object.
*
* @param interfaces the class name of the interface for the object to create the proxy for
* @param impl the class name of the the object to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @return the new remote proxy instance
*/
public static RemoteProxy createClientProxy(final String[] interfaces,
final String impl,
final String address,
final int port) {
return RemoteProxy.createClientProxy(
interfaces, impl, address, port, Thread.currentThread()
.getContextClassLoader()
);
}
/**
* Creates a new proxy to a class. To be used on the client side to create a new proxy to an object.
*
* @param interfaces the class name of the interface for the object to create the proxy for
* @param impl the class name of the the object to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @param context the context carrying the users principal and credentials
* @return the new remote proxy instance
*/
public static RemoteProxy createClientProxy(final String[] interfaces,
final String impl,
final String address,
final int port,
final Object context) {
return RemoteProxy.createClientProxy(
interfaces, impl, address, port, context, Thread.currentThread()
.getContextClassLoader()
);
}
/**
* Creates a new proxy to a class. To be used on the client side to create a new proxy to an object.
*
* @param interfaces the class name of the interface for the object to create the proxy for
* @param impl the class name of the the object to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @param loader the class loader to use
* @return the new remote proxy instance
*/
public static RemoteProxy createClientProxy(final String[] interfaces,
final String impl,
final String address,
final int port,
final ClassLoader loader) {
return RemoteProxy.createClientProxy(interfaces, impl, address, port, null, loader);
}
/**
* Creates a new proxy to a class. To be used on the client side to create a new proxy to an object.
*
* @param interfaces the class name of the interface for the object to create the proxy for
* @param impl the class name of the the object to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @param ctx the context carrying the users principal and credentials
* @param loader the class loader to use
* @return the new remote proxy instance
*/
public static RemoteProxy createClientProxy(final String[] interfaces,
final String impl,
final String address,
final int port,
final Object context,
final ClassLoader loader) {
return new RemoteProxy(interfaces, impl, address, port, context, loader);
}
/**
* Creates a proxy to a specific <b>instance </b> in the on the server side. This proxy could then be passed to the
* client which can invoke method on this specific <b>instance </b>.
*
* @param the target instance to create the proxy for
* @param address the address to connect to.
* @param port the port to connect to.
* @return the new remote proxy instance
*/
public static RemoteProxy createServerProxy(final Object targetlInstance, final String address, final int port) {
return new RemoteProxy(targetlInstance, address, port);
}
/**
* Look up and retrives a proxy to an object from the server.
*
* @param loader the classloader to use
* @return the proxy instance
*/
public Object getInstance(final ClassLoader loader) {
m_loader = loader;
return getInstance();
}
/**
* Look up and retrives a proxy to an object from the server.
*
* @return the proxy instance
*/
public Object getInstance() {
if (m_proxy != null) {
return m_proxy;
}
if (m_loader == null) {
m_loader = Thread.currentThread().getContextClassLoader();
}
try {
m_socket = new Socket(InetAddress.getByName(m_address), m_port);
m_socket.setTcpNoDelay(true);
m_out = new ObjectOutputStream(m_socket.getOutputStream());
m_in = new ObjectInputStream(m_socket.getInputStream());
} catch (Exception e) {
throw new WrappedRuntimeException(e);
}
if (m_handle == null) {
// is a client side proxy
if (m_targetInterfaceNames == null) {
throw new IllegalStateException("interface class name can not be null");
}
if (m_targetImplName == null) {
throw new IllegalStateException("implementation class name can not be null");
}
try {
// create a new instance on the server and get the handle to it in return
m_out.write(Command.CREATE);
m_out.writeObject(m_targetImplName);
m_out.flush();
m_handle = (String) m_in.readObject();
m_targetInterfaces = new Class[m_targetInterfaceNames.length];
for (int i = 0; i < m_targetInterfaceNames.length; i++) {
try {
m_targetInterfaces[i] = Class.forName(m_targetInterfaceNames[i], false, m_loader);
} catch (ClassNotFoundException e) {
throw new WrappedRuntimeException(e);
}
}
} catch (Exception e) {
throw new WrappedRuntimeException(e);
}
}
m_proxy = Proxy.newProxyInstance(m_loader, m_targetInterfaces, this);
return m_proxy;
}
/**
* This method is invoked automatically by the proxy. Should not be called directly.
*
* @param proxy the proxy instance that the method was invoked on
* @param method the Method instance corresponding to the interface method invoked on the proxy instance.
* @param args an array of objects containing the values of the arguments passed in the method invocation on the
* proxy instance.
* @return the value to return from the method invocation on the proxy instance.
*/
public Object invoke(final Object proxy, final Method method, final Object[] args) {
try {
m_out.write(Command.INVOKE);
m_out.writeObject(m_context);
m_out.writeObject(m_handle);
m_out.writeObject(method.getName());
m_out.writeObject(method.getParameterTypes());
m_out.writeObject(args);
m_out.flush();
final Object response = m_in.readObject();
if (response instanceof Exception) {
throw (Exception) response;
}
return response;
} catch (Exception e) {
throw new WrappedRuntimeException(e);
}
}
/**
* Closes the proxy and the connection to the server.
*/
public void close() {
try {
m_out.write(Command.CLOSE);
m_out.flush();
m_out.close();
m_in.close();
m_socket.close();
} catch (IOException e) {
throw new WrappedRuntimeException(e);
}
}
/**
* Returns a proxy wrapped instance by its handle.
*
* @param handle the handle
* @return the instance
*/
public static Object getWrappedInstance(final String handle) {
return s_instances.get(handle);
}
/**
* Wraps a new instance and maps it to a handle.
*
* @param instance the instance to wrap
* @return the handle for the instance
*/
public static String wrapInstance(final Object instance) {
final String handle = UuidGenerator.generate(instance);
s_instances.put(handle, instance);
return handle;
}
}