/*
* 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.aspects.remoting;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import javax.naming.InitialContext;
import org.jboss.aop.Dispatcher;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.proxy.Proxy;
import org.jboss.logging.Logger;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.transport.Connector;
import org.jboss.util.naming.Util;
/**
* <code>RemotingProxyFactory</code> is an AOP / Remoting replacement for
* <code>org.jboss.invocation.jrmp.server.JRMPProxyFactory</code>.
* <p>
* Given the following parameters
* <p>
* <ul>
* <li>a list of interfaces,</li>
* <li>an array of interceptor class names or interceptor POJOs,</li>
* <li>an <code>org.jboss.remoting.InvokerLocator</code> pointing to an
* <code>org.jboss.remoting.transport.Connector</code> running
* an <code>org.jboss.aspects.remoting.AOPRemotingInvocationHandler</code>,</li>
* <li>a target object, and</li>
* <li>a JNDI name,</li>
* </ul>
* <p>
* RemotingProxyFactory will create a proxy that
* <p>
* <ol>
* <li>implements the interfaces,</li>
* <li>makes invocations through the chain of client side interceptors,<li>
* <li>sends invocations to the remote <code>Connector</code> where they are processed
* by the provided target,</li>
* </ol>
* <p>
* and it will bind the proxy to the provided name in JNDI.
*
* @author <a href="ron.sigal@jboss.com">Ron Sigal</a>
* @version $Revision: 1.1 $
* <p>
* Copyright Jun 6, 2008
* </p>
*/
public class RemotingProxyFactory
{
private static final Logger log = Logger.getLogger(RemotingProxyFactory.class);
private Object target;
private Class<?>[] interfaces;
private String dispatchName;
private String jndiName;
private InvokerLocator invokerLocator;
private Connector connector;
private String subsystem = "AOP";
private ArrayList<?> interceptors;
private ArrayList<Interceptor> verifiedInterceptors;
private Proxy proxy;
/**
* Returns the target to which the AOP dispatcher will direct invocations
* <p>
* @return the target to which the AOP dispatcher will direct invocations
*/
public Object getTarget()
{
return target;
}
/**
* Sets the target to which the AOP dispatcher will direct invocations
* <p>
* @param target the target to which the AOP dispatcher will direct invocations
*/
public void setTarget(Object target)
{
this.target = target;
}
/**
* Returns the interfaces implemented by the proxy created by this instance
* of <code>RemotingProxyFactory</code>
* <p>
* @return the interfaces implemented by the proxy created by this instance
* of <code>RemotingProxyFactory</code>s
*/
public Class<?>[] getInterfaces()
{
return interfaces;
}
/**
* Sets the interfaces implemented by the proxy created by this
* instance of <code>RemotingProxyFactory</code>
* <p>
* @param interfaces the interfaces implemented by the proxy created by this
* instance of <code>RemotingProxyFactory</code>
*/
public void setInterfaces(Class<?>[] interfaces)
{
this.interfaces = interfaces;
for (int i = 0; i < interfaces.length; i++)
log.debug("interface[" + i + "]: " + interfaces[i]);
}
/**
* Returns the name under which the AOP dispatcher will register the target
* <p>
* @return the name under which the AOP dispatcher will register the target
*/
public String getDispatchName()
{
return dispatchName;
}
/**
* Sets the name under which the AOP dispatcher will register the target
* <p>
* @param dispatchName the name under which the AOP dispatcher will register the target
*/
public void setDispatchName(String dispatchName)
{
this.dispatchName = dispatchName;
}
/**
* Returns the name to which the proxy will be bound in JNDI
* <p>
* @return the name to which the proxy will be bound in JNDI
*/
public String getJndiName()
{
return jndiName;
}
/**
* Sets the name to which the proxy will be bound in JNDI
* <p>
* @param jndiName the name to which the proxy will be bound in JNDI
*/
public void setJndiName(String jndiName)
{
this.jndiName = jndiName;
}
/**
* Returns the String form of the <code>InvokerLocator</code> that identifies the
* Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
* <p>
* @return the String form of the <code>InvokerLocator</code> that identifies the
* Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
*/
public String getInvokerLocator()
{
return invokerLocator.toString();
}
/**
* Sets the String form of the <code>InvokerLocator</code> that identifies the
* Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
* <p>
* @param locator the String form of the <code>InvokerLocator</code> that identifies the
* Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
* @throws MalformedURLException
*/
public void setInvokerLocator(String locator) throws MalformedURLException
{
this.invokerLocator = new InvokerLocator(locator);
}
/**
* Returns the Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
* <p>
* @return the Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
*/
public Connector getConnector()
{
return connector;
}
/**
* Sets the Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
* <p>
* @param connector the Remoting <code>Connector</code> that directs invocations to the
* <code>AOPRemotingInvocationHandler</code>, which then directs
* invocations to the target
*/
public void setConnector(Connector connector)
{
this.connector = connector;
}
/**
* Returns the subsystem name that identifes the <code>AOPRemotingInvocationHandler</code>
* running in the Remoting <code>Connector</code>. Defaults to "AOP".
* <p>
* @return the subsystem name that identifes the <code>AOPRemotingInvocationHandler</code>
* running in the Remoting <code>Connector</code>. Defaults to "AOP".
*/
public String getSubsystem()
{
return subsystem;
}
/**
* Sets the subsystem name that identifes the <code>AOPRemotingInvocationHandler</code>
* running in the Remoting <code>Connector</code>. Defaults to "AOP".
* <p>
* @param subsystem the subsystem name that identifes the <code>AOPRemotingInvocationHandler</code>
* running in the Remoting <code>Connector</code>. Defaults to "AOP".
*/
public void setSubsystem(String subsystem)
{
this.subsystem = subsystem;
}
/**
* Returns the interceptors through which an invocation will pass on the client side.
* <p>
* <b>N.B.</b> Each element of the list is either
* <p>
* <ol>
* <li>the fully qualified class name of an interceptor, or</li>
* <li>an interceptor POJO.</li>
* </ol>
* <p>
* @return the interceptors through which an invocation will pass on the client side.
*
*/
public ArrayList<?> getInterceptors()
{
return interceptors;
}
/**
* Sets the interceptors through which an invocation will pass on the client side.
* <p>
* <b>N.B.</b> Each element of the list must be either
* <p>
* <ol>
* <li>the fully qualified class name of an interceptor, or</li>
* <li>an interceptor POJO.</li>
* </ol>
* <p>
* If the element is a class name, then the class must have either
* <p>
* <ol>
* <li>a public static field named "singleton" that holds an instance of the interceptor, or</li>
* <li>a default constructor.</li>
* </ol>
* <p>
* <b>N.B.</b>The interceptors <code>MergeMetaDataInterceptor</code> and
* <code>InvokeRemoteInterceptor</code> are automatically appended to the end of the list.
* <p>
* @param interceptors the interceptors through which an invocation will pass on the client side.
*/
public void setInterceptors(ArrayList<?> interceptors)
{
this.interceptors = interceptors;
}
/**
* Returns the proxy created by this instance of <code>RemotingProxyFactory</code>
* <p>
* @return the proxy created by this instance of <code>RemotingProxyFactory</code>
*/
public Proxy getProxy()
{
return proxy;
}
/**
* Lifecycle method.
* <p>
* The lifecycle method that
* <ol>
* <li>registers the target with the AOP dispatcher</li>
* <li>creates the proxy</li>
* <li>binds the proxy in JNDI</li>
* </ol>
* <p>
* @throws Exception
*/
public void start() throws Exception
{
doSanityChecks();
verifyInterceptors();
// Register invocation target.
Dispatcher.singleton.registerTarget(dispatchName, target);
// Create proxy.
ClassLoader loader = getContextClassLoader();
proxy = Remoting.createRemoteProxy(dispatchName, loader, interfaces, invokerLocator, verifiedInterceptors, subsystem);
log.debug("Created proxy for " + dispatchName);
// Bind proxy in JNDI.
InitialContext ctx = new InitialContext();
Util.bind(ctx, jndiName, proxy);
log.debug("Bound proxy for " + dispatchName + " to " + jndiName);
}
/**
* Lifecycle method.
* <p>
* The lifecycle method that
* <p>
* <ol>
* <li>unregisters the target with AOP dispatcher</li>
* <li>unbinds the proxy from JNDI</li>
* </ol>
* <p>
* @throws Exception
*/
public void stop() throws Exception
{
Dispatcher.singleton.unregisterTarget(dispatchName);
InitialContext ctx = new InitialContext();
Util.unbind(ctx, jndiName);
log.debug("Unbound proxy for " + dispatchName);
}
private void doSanityChecks() throws Exception
{
if (target == null)
throw new Exception("Cannot start factory: target == null");
if (interfaces == null)
throw new Exception("Cannot start factory: interfaces == null");
if (interfaces.length == 0)
throw new Exception("Cannot start factory: interfaces is empty array");
for (int i = 0; i < interfaces.length; i++)
{
if (!(interfaces[i].isInstance(target)))
throw new Exception("Cannot start factory: " + target + " does not implement " + interfaces[i]);
}
if (dispatchName == null)
throw new Exception("Cannot start factory: dispatchName == null");
if (jndiName == null)
throw new Exception("Cannot start factory: jndiName == null");
if (invokerLocator == null && connector == null)
throw new Exception("Cannot start factory: locator == null and connector == null");
if (invokerLocator == null)
invokerLocator = connector.getLocator();
}
private void verifyInterceptors() throws Exception
{
verifiedInterceptors = new ArrayList<Interceptor>(interceptors.size());
Interceptor interceptor = null;
ClassLoader tcl = getContextClassLoader();
for (Object o : interceptors)
{
log.debug("processing interceptor: " + o);
if (o instanceof String)
{
Class<?> c = Class.forName((String) o, true, tcl);
try
{
Field field = c.getDeclaredField("singleton");
interceptor = (Interceptor) field.get(null);
}
catch (Exception e)
{
log.debug(c.getName() + " has no singleton element: trying default constructor");
interceptor = (Interceptor) c.newInstance();
}
if (o == null)
{
throw new Exception("Cannot start factory: unable to create instance of " + c.getName());
}
}
else if (o instanceof Interceptor)
{
interceptor = (Interceptor) o;
}
else
{
throw new Exception(o + " is neither String nor Interceptor");
}
verifiedInterceptors.add(interceptor);
log.debug("added interceptor: " + interceptor);
}
interceptor = MergeMetaDataInterceptor.singleton;
verifiedInterceptors.add(interceptor);
log.debug("added interceptor: " + interceptor);
interceptor = InvokeRemoteInterceptor.singleton;
verifiedInterceptors.add(interceptor);
log.debug("added interceptor: " + interceptor);
}
private ClassLoader getContextClassLoader()
{
return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction<Object>()
{
public Object run()
{
return Thread.currentThread().getContextClassLoader();
}
});
}
}