/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.security;
import java.lang.reflect.Method;
import java.util.HashMap;
import javax.ejb.EJBContext;
/**
* An abstract implementation of SecurityProxy that wraps a non-SecurityProxy
* object. Subclasses of this class are used to create a SecurityProxy given
* a security delegate that implements methods in the EJB home or remote
* interface for security checks. This allows custom security classes to be
* written without using a JBoss specific interface. It also allows the security
* delegate to follow a natural proxy pattern implementation.
*
* @author Scott.Stark@jboss.org
* @version $Revision: 85945 $
*/
public abstract class AbstractSecurityProxy implements SecurityProxy
{
/** The HashMap<Method, Method> from the EJB interface methods to the
* corresponding delegate method
*/
private HashMap methodMap;
/** The optional setContext delegate method */
private Method setContextMethod;
/** The optional setContext delegate method */
private Method setBeanMethod;
/** The optional setContext delegate method */
protected Object delegate;
/** Flag which sets whether the method mapping will be performed in a strict
* fashion. The proxy delegate must provide an implementation of all methods.
* If set to 'true', a security exception will be thrown during
* initialisation if a method is found for which the delegate doesn't have
* a matching method. This defaults to false and is obtained via reflection
* on the proxy delegate's 'boolean isStrict()' method.
*/
protected boolean strict = false;
AbstractSecurityProxy(Object delegate)
{
this.delegate = delegate;
methodMap = new HashMap();
}
/**
* Subclasses implement this method to actually invoke the given home
* method on the proxy delegate.
*
* @param m, the delegate method that was mapped from the ejb home method.
* @param args, the method invocation arguments.
* @param delegate, the proxy delegate object associated with the
* AbstractSecurityProxy
*
* @see invokeHome(Method, Object[])
*/
protected abstract void invokeHomeOnDelegate(Method m, Object[] args,
Object delegate) throws Exception;
/**
* Subclasses implement this method to actually invoke the given remote
* method on the proxy delegate.
*
* @param m, the delegate method that was mapped from the ejb remote method.
* @param args, the method invocation arguments.
* @param delegate, the proxy delegate object associated with the AbstractSecurityProxy
*
* @see invoke(Method, Object[], Object)
*/
protected abstract void invokeOnDelegate(Method m, Object[] args, Object delegate)
throws Exception;
/**
*
* This version invokes init(beanHome, beanRemote, null, null, securityMgr)
*
* @see #init(Class, Class, Class, Class, Object)
* @param beanHome, the class for the EJB home interface
* @param beanRemote, the class for the EJB remote interface
* @param securityMgr, The security manager instance assigned to the container.
* It is not used by this class.
*/
public void init(Class beanHome, Class beanRemote, Object securityMgr)
throws InstantiationException
{
init(beanHome, beanRemote, null, null, securityMgr);
}
/** This method is called by the container SecurityInterceptor to intialize
* the proxy with the EJB home and remote interface classes that the
* container is housing. This method creates a mapping from the home and
* remote classes to the proxy delegate instance. The mapping is based on
* method name and paramter types. In addition, the proxy delegate is
* inspected for a setEJBContext(EJBContext) and a setBean(Object) method
* so that the active EJBContext and EJB instance can be passed to the
* delegate prior to method invocations.
*
* @param beanHome The EJB remote home interface class
* @param beanRemote The EJB remote interface class
* @param beanLocalHome The EJB local home interface class
* @param beanLocal The EJB local interface class
* @param securityMgr The security manager from the security domain
* @throws InstantiationException
*/
public void init(Class beanHome, Class beanRemote,
Class beanLocalHome, Class beanLocal, Object securityMgr)
throws InstantiationException
{
// Get any methods from the bean home interface
mapHomeMethods(beanHome);
// Get any methods from the bean local home interface
mapHomeMethods(beanLocalHome);
// Get any methods from the bean remote interface
mapRemoteMethods(beanRemote);
// Get any methods from the bean local interface
mapRemoteMethods(beanLocal);
// Get the setEJBContext(EJBContext) method
try
{
Class[] parameterTypes = {EJBContext.class};
setContextMethod = delegate.getClass().getMethod("setEJBContext", parameterTypes);
}
catch(Exception ignore)
{
}
// Get the setBean(Object) method
try
{
Class[] parameterTypes = {Object.class};
setBeanMethod = delegate.getClass().getMethod("setBean", parameterTypes);
}
catch(Exception ignore)
{
}
// Check for a boolean isStrict() strict flag accessor
try
{
Class[] parameterTypes = {};
Object[] args = {};
Method isStrict = delegate.getClass().getMethod("isStrict", parameterTypes);
Boolean flag = (Boolean) isStrict.invoke(delegate, args);
strict = flag.booleanValue();
}
catch(Exception ignore)
{
}
}
/** Called by the SecurityProxyInterceptor prior to a method invocation
* to set the context for the call.
*
* @param ctx the bean's EJBContext
*/
public void setEJBContext(EJBContext ctx)
{
if(setContextMethod != null)
{
Object[] args = {ctx};
try
{
setContextMethod.invoke(delegate, args);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
/** Called by the SecurityProxyInterceptor to allow the proxy delegate to
* perform a security check of the indicated home interface method.
*
* @param m, the EJB home interface method
* @param args, the method arguments
*/
public void invokeHome(final Method m, Object[] args)
throws Exception
{
Method delegateMethod = (Method)methodMap.get(m);
if( delegateMethod != null )
invokeHomeOnDelegate(delegateMethod, args, delegate);
}
/**
* Called by the SecurityProxyInterceptor to allow the proxy delegate to perform
* a security check of the indicated remote interface method.
* @param m, the EJB remote interface method
* @param args, the method arguments
* @param bean, the EJB bean instance
*/
public void invoke(final Method m, final Object[] args, final Object bean)
throws Exception
{
Method delegateMethod = (Method)methodMap.get(m);
if( delegateMethod != null )
{
if( setBeanMethod != null )
{
Object[] bargs = {bean};
try
{
setBeanMethod.invoke(delegate, bargs);
}
catch(Exception e)
{
e.printStackTrace();
throw new SecurityException("Failed to set bean on proxy" + e.getMessage());
}
}
invokeOnDelegate(delegateMethod, args, delegate);
}
}
/** Performs a mapping from the methods declared in the beanHome class to
* the proxy delegate class. This allows the methods to be either named
* the same as the home interface method "create(...)" or as the bean
* class method "ejbCreate(...)". This handles both local home and
* remote home interface methods.
*/
protected void mapHomeMethods(Class beanHome)
{
if( beanHome == null )
return;
Class delegateClass = delegate.getClass();
Method[] methods = beanHome.getMethods();
for(int m = 0; m < methods.length; m++)
{
// Check for ejbCreate... methods
Method hm = methods[m];
Class[] parameterTypes = hm.getParameterTypes();
String name = hm.getName();
name = "ejb" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
try
{
Method match = delegateClass.getMethod(name, parameterTypes);
methodMap.put(hm, match);
}
catch(NoSuchMethodException e)
{
// Try for the home interface name without the ejb prefix
name = hm.getName();
try
{
Method match = delegateClass.getMethod(name, parameterTypes);
methodMap.put(hm, match);
}
catch(NoSuchMethodException e2)
{
if( strict )
{
String msg = "Missing home method:" + hm + " in delegate";
throw new SecurityException(msg);
}
}
}
}
}
/** Performs a mapping from the methods declared in the beanRemote class to
* the proxy delegate class. This handles both local and remote interface
* methods.
*/
protected void mapRemoteMethods(Class beanRemote)
{
if( beanRemote == null )
return;
Class delegateClass = delegate.getClass();
Method[] methods = beanRemote.getMethods();
for(int m = 0; m < methods.length; m++)
{
Method rm = methods[m];
Class[] parameterTypes = rm.getParameterTypes();
String name = rm.getName();
try
{
Method match = delegateClass.getMethod(name, parameterTypes);
methodMap.put(rm, match);
}
catch(NoSuchMethodException e)
{
if( strict )
{
String msg = "Missing method:" + rm + " in delegate";
throw new SecurityException(msg);
}
}
}
}
}