/*
* Class org.jboss.verifier.strategy.AbstractVerifier
* Copyright (C) 2000 Juha Lindfors
*
* This library 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 of the License, or (at your option) any later version
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* This package and its source code is available at www.jboss.org
* $Id: AbstractVerifier.java,v 1.33.2.6 2003/10/24 13:00:12 loubyansky Exp $
*/
package org.jboss.verifier.strategy;
// standard imports
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.Arrays;
import java.net.URL;
import java.net.URLClassLoader;
// non-standard class dependencies
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.EntityMetaData;
import org.jboss.metadata.MessageDrivenMetaData;
import org.jboss.metadata.SessionMetaData;
import org.jboss.verifier.factory.VerificationEventFactory;
import org.jboss.verifier.event.VerificationEvent;
import org.jboss.verifier.Section;
import org.jboss.logging.Logger;
import org.gjt.lindfors.pattern.StrategyContext;
/**
* Abstract superclass for verifiers containing a bunch of useful methods.
*
* @see org.jboss.verifier.strategy.VerificationStrategy
*
* @author Juha Lindfors
* @author Aaron Mulder (ammulder@alumni.princeton.edu)
* @author Vinay Menon (menonv@cpw.co.uk)
* @author Andreas Schaefer
* @author Luke Taylor
* @author Jay Walters
*
*
20020210: luke
*
* - Changed hasRemoteReturnType() to use isAssignableFrom() rather
* than just compare strings. See Bug 489554 (sub-classed remote
* return types should be OK)
*
*
*
* 20020404: jwalters
*
* - Added new helper methods for Local home and interface matching
* pre-existing methods for Remote home and interface.
*
*
* 20020404: luke
*
* - Changed hasDefaultConstructor to use reflection rather than calling
* newInstance. The latter won't work with abstract classes.
*
*
*
* @version $Revision: 1.33.2.6 $
* @since JDK 1.3
*/
public abstract class AbstractVerifier
implements VerificationStrategy
{
static final Logger log = Logger.getLogger(AbstractVerifier.class);
protected final static String EJB_OBJECT_INTERFACE =
"javax.ejb.EJBObject";
protected final static String EJB_HOME_INTERFACE =
"javax.ejb.EJBHome";
protected final static String EJB_LOCAL_OBJECT_INTERFACE =
"javax.ejb.EJBLocalObject";
protected final static String EJB_LOCAL_HOME_INTERFACE =
"javax.ejb.EJBLocalHome";
/**
* The application classloader. This can be provided by the context
* directly via {@link VerificationContext#getClassLoader} method, or
* constructed by this object by creating a classloader to the URL
* returned by {@link VerificationContext#getJarLocation} method.
*
* Initialized in the constructor.
*/
protected ClassLoader classloader = null;
/**
* Factory for generating the verifier events.
*
* Initialized in the constructor.
*
* @see org.jboss.verifier.factory.DefaultEventFactory
*/
private VerificationEventFactory factory = null;
/**
* Context is used for retrieving application level information,
* such as the application meta data, location of the jar file, etc.
*
*
* Initialized in the constructor.
*/
private VerificationContext context = null;
/*
*************************************************************************
*
* CONSTRUCTORS
*
*************************************************************************
*/
public AbstractVerifier( VerificationContext context,
VerificationEventFactory factory)
{
this.factory = factory;
this.context = context;
this.classloader = context.getClassLoader();
if( this.classloader == null )
{
URL[] list = { context.getJarLocation() };
ClassLoader parent = Thread.currentThread().getContextClassLoader();
this.classloader = new URLClassLoader(list, parent);
}
}
/*
*************************************************************************
*
* PUBLIC INSTANCE METHODS
*
*************************************************************************
*/
public boolean isAssignableFrom(String className, Class assignableFromClass)
{
try
{
Class clazz = this.classloader.loadClass(className);
return clazz.isAssignableFrom(assignableFromClass);
}
catch(ClassNotFoundException e)
{
log.warn("Failed to find class: "+className, e);
}
return false;
}
public boolean isAssignableFrom(Class clazz, String assignableFromClassName)
{
try
{
Class assignableFromClass = this.classloader.loadClass(assignableFromClassName);
return clazz.isAssignableFrom(assignableFromClass);
}
catch(ClassNotFoundException e)
{
log.warn("Failed to find class: "+assignableFromClassName, e);
}
return false;
}
public abstract boolean isCreateMethod(Method m);
public abstract boolean isEjbCreateMethod(Method m);
public boolean hasLegalRMIIIOPArguments( Method method )
{
Class[] params = method.getParameterTypes();
for (int i = 0; i < params.length; ++i) {
if (!isRMIIIOPType(params[i]))
return false;
}
return true;
}
public boolean hasLegalRMIIIOPReturnType(Method method)
{
return isRMIIIOPType(method.getReturnType());
}
public boolean hasLegalRMIIIOPExceptionTypes(Method method)
{
/*
* All checked exception classes used in method declarations
* (other than java.rmi.RemoteException) MUST be conforming
* RMI/IDL exception types.
*
* Spec 28.2.3 (4)
*/
Iterator it = Arrays.asList(method.getExceptionTypes()).iterator();
while (it.hasNext()) {
Class exception = (Class)it.next();
if (!isRMIIDLExceptionType(exception))
return false;
}
return true;
}
/**
* Checks if the method includes java.rmi.RemoteException or its
* subclass in its throws clause.
*
* See bug report #434739 and #607805
*/
public boolean throwsRemoteException(Method method)
{
Class[] exception = method.getExceptionTypes();
for (int i = 0; i < exception.length; ++i)
{
// Fix for bug #607805: an IOException is OK for local interfaces
// Fix for bug #626430: java.lang.Exception is also OK
if( exception[i].equals(java.io.IOException.class)
|| exception[i].equals(java.lang.Exception.class) )
{
continue;
}
// Not true see bug report #434739
// if (java.rmi.RemoteException.class.isAssignableFrom(exception[i]))
// According to the RMI spec. a remote interface must throw an RemoteException
// or any of its super classes therefore the check must be done vice versa
if( isAssignableFrom(exception[i], "java.rmi.RemoteException") )
{
return true;
}
}
return false;
}
/**
* checks if the method accepts a single parameter of a specified type.
*/
public boolean hasSingleArgument(Method method, Class argClass)
{
Class[] params = method.getParameterTypes();
if (params.length == 1)
{
if( params[0].equals(argClass) )
return true;
}
return false;
}
/**
* checks if the method accepts any parameters.
*/
public boolean hasNoArguments(Method method)
{
Class[] params = method.getParameterTypes();
return (params.length == 0) ? true : false;
}
/**
* checks if the method throws no checked exceptions in its throws clause.
*/
public boolean throwsNoException(Method method)
{
boolean hasCheckedException = false;
Class[] exceptions = method.getExceptionTypes();
for(int e = 0; e < exceptions.length; e ++)
{
Class ex = exceptions[e];
boolean isError = Error.class.isAssignableFrom(ex);
boolean isRuntimeException = RuntimeException.class.isAssignableFrom(ex);
if( isError == false && isRuntimeException == false )
hasCheckedException = true;
}
return hasCheckedException == false;
}
/**
* checks if the method includes java.ejb.CreateException in its
* throws clause.
*/
public boolean throwsCreateException(Method method)
{
Class[] exception = method.getExceptionTypes();
for (int i = 0; i < exception.length; ++i)
{
if( isAssignableFrom("javax.ejb.CreateException", exception[i]))
return true;
}
return false;
}
/**
* checks if the methods includes javax.ejb.FinderException in its
* throws clause.
*/
public boolean throwsFinderException(Method method)
{
Class[] exception = method.getExceptionTypes();
for (int i = 0; i < exception.length; ++i)
{
if( isAssignableFrom("javax.ejb.FinderException", exception[i]))
return true;
}
return false;
}
/**
* checks if a class's member (method, constructor or field) has a
* static modifier.
*/
public boolean isStatic( Member member )
{
return (Modifier.isStatic(member.getModifiers()));
}
/**
* checks if the given class is declared as static (inner classes only)
*/
public boolean isStatic( Class c )
{
return (Modifier.isStatic(c.getModifiers()));
}
/**
* checks if a class's member (method, constructor or field) has a
* final modifier.
*/
public boolean isFinal( Member member )
{
return (Modifier.isFinal(member.getModifiers()));
}
/**
* checks if the given class is declared as final
*/
public boolean isFinal( Class c )
{
return (Modifier.isFinal(c.getModifiers()));
}
/**
* checks if a class's member (method, constructor or field) has a
* public modifier.
*/
public boolean isPublic( Member member )
{
return (Modifier.isPublic(member.getModifiers()));
}
/**
* checks if the given class is declared as public
*/
public boolean isPublic( Class c )
{
return (Modifier.isPublic(c.getModifiers()));
}
/**
* Checks whether all the fields in the class are declared as public.
*/
public boolean isAllFieldsPublic( Class c )
{
try
{
Field list[] = c.getFields();
for(int i=0; inull
* if none is found
*/
public Method getDefaultCreateMethod( Class c )
{
Method method = null;
try
{
method = c.getMethod(CREATE_METHOD, null);
}
catch (NoSuchMethodException ignored)
{}
return method;
}
/**
* Returns the ejbFindByPrimaryKey method
*/
public Method getEJBFindByPrimaryKey( Class c )
{
Method[] method = c.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (method[i].getName().equals(EJB_FIND_BY_PRIMARY_KEY))
return method[i];
}
return null;
}
/**
* returns the ejbFind methods of a bean
*/
public Iterator getEJBFindMethods( Class c )
{
List finders = new LinkedList();
Method[] method = c.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (method[i].getName().startsWith("ejbFind"))
finders.add(method[i]);
}
return finders.iterator();
}
/**
* returns the finder methods of a home interface
*/
public Iterator getFinderMethods( Class home )
{
List finders = new LinkedList();
Method[] method = home.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (method[i].getName().startsWith("find"))
finders.add(method[i]);
}
return finders.iterator();
}
/**
* Returns the onMessage(...) method of a bean
*/
public Iterator getOnMessageMethods(Class c)
{
List onMessages = new LinkedList();
Method[] method = c.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (isOnMessageMethod(method[i]))
onMessages.add(method[i]);
}
return onMessages.iterator();
}
/**
* Returns the ejbCreate(...) methods of a bean
*/
public Iterator getEJBCreateMethods( Class c )
{
List ejbCreates = new LinkedList();
Method[] method = c.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (isEjbCreateMethod(method[i]))
ejbCreates.add(method[i]);
}
return ejbCreates.iterator();
}
/**
* Return all create methods of a class
*/
public Iterator getCreateMethods( Class c )
{
List creates = new LinkedList();
Method[] method = c.getMethods();
for (int i = 0; i < method.length; ++i)
{
if (isCreateMethod(method[i]))
creates.add(method[i]);
}
return creates.iterator();
}
/**
* Check whether a class has more than one create method
*/
public boolean hasMoreThanOneCreateMethods( Class c )
{
int count = 0;
Method[] method = c.getMethods();
for( int i = 0; i < method.length; ++i )
{
if (isCreateMethod(method[i]))
{
++count;
}
}
return (count > 1);
}
/**
* Check whether two given methods declare the same Exceptions
*/
public boolean hasMatchingExceptions( Method source, Method target )
{
// target must be a superset of source
Class[] a = source.getExceptionTypes();
Class[] b = target.getExceptionTypes();
Class rteClass = null;
Class errorClass = null;
try
{
rteClass = classloader.loadClass( "java.lang.RuntimeException" );
errorClass = classloader.loadClass( "java.lang.Error" );
}
catch( ClassNotFoundException cnfe )
{
// Ignored, if this happens we have more serious problems :)
}
for( int i = 0; i < a.length; ++i )
{
if( rteClass.isAssignableFrom(a[i])
|| errorClass.isAssignableFrom(a[i]) )
{
// Skip over subclasses of java.lang.RuntimeException and
// java.lang.Error
continue;
}
boolean found = false;
for( int j = 0; j < b.length; ++j )
{
if( b[j].isAssignableFrom (a[i]) )
{
found = true;
break;
}
}
if ( !found )
{
return false;
}
}
return true;
}
/**
* Check if a class (or its superclasses) declare a given method
*/
public boolean hasMatchingMethod( Class bean, Method method )
{
try
{
bean.getMethod( method.getName(), method.getParameterTypes() );
return true;
}
catch (NoSuchMethodException e)
{
return false;
}
}
/**
* Check whether two methods have the same return type
*/
public boolean hasMatchingReturnType( Method a, Method b )
{
return (a.getReturnType() == b.getReturnType());
}
/**
* Check whether a bean has a matching ejbPostCreate methods for
* a given ejbCreate method
*/
public boolean hasMatchingEJBPostCreate( Class bean, Method create )
{
try
{
return (bean.getMethod(getMatchingEJBPostCreateName(create.getName()),
create.getParameterTypes()) != null);
}
catch (NoSuchMethodException e)
{
return false;
}
}
public boolean hasMatchingEJBCreate( Class bean, Method create )
{
try
{
return (bean.getMethod(getMatchingEJBCreateName(create.getName()), create.getParameterTypes()) != null);
}
catch (NoSuchMethodException e)
{
return false;
}
}
public Method getMatchingEJBPostCreate( Class bean, Method create )
{
try
{
return bean.getMethod(getMatchingEJBPostCreateName(create.getName()), create.getParameterTypes());
}
catch (NoSuchMethodException e)
{
return null;
}
}
public Method getMatchingEJBCreate( Class bean, Method create )
{
try
{
return bean.getMethod(getMatchingEJBCreateName(create.getName()), create.getParameterTypes());
}
catch (NoSuchMethodException e)
{
return null;
}
}
public boolean hasMatchingEJBFind(Class bean, Method finder)
{
try
{
String methodName = "ejbF" + finder.getName().substring(1);
return (bean.getMethod(methodName, finder.getParameterTypes()) != null);
}
catch (NoSuchMethodException e)
{
return false;
}
}
public Method getMatchingEJBFind(Class bean, Method finder)
{
try
{
String methodName = "ejbF" + finder.getName().substring(1);
return bean.getMethod(methodName, finder.getParameterTypes());
}
catch (NoSuchMethodException e)
{
return null;
}
}
public boolean hasMatchingEJBHome(Class bean, Method home)
{
try
{
return (bean.getMethod(getMatchingEJBHomeName(home.getName()), home.getParameterTypes()) != null);
}
catch (NoSuchMethodException e)
{
return false;
}
}
/*
*************************************************************************
*
* PROTECTED INSTANCE METHODS
*
*************************************************************************
*/
protected void fireSpecViolationEvent( BeanMetaData bean, Section section )
{
fireSpecViolationEvent(bean, null /* method */, section);
}
protected void fireSpecViolationEvent(BeanMetaData bean, Method method,
Section section)
{
VerificationEvent event = factory.createSpecViolationEvent(context,
section);
event.setName(bean.getEjbName());
event.setMethod(method);
context.fireSpecViolation(event);
}
protected final void fireBeanVerifiedEvent( BeanMetaData bean )
{
fireBeanVerifiedEvent( bean, null );
}
protected final void fireBeanVerifiedEvent( BeanMetaData bean, String msg )
{
VerificationEvent event = factory.createBeanVerifiedEvent(context);
event.setName(bean.getEjbName());
if( msg != null )
{
event.setMessage( msg );
}
context.fireBeanChecked(event);
}
/*
*************************************************************************
*
* IMPLEMENTS VERIFICATIONSTRATEGY INTERFACE
*
*************************************************************************
*/
/**
* Provides an empty default implementation for EJB 1.1 verifier (message
* beans are for EJB 2.0 and greater only).
*
* @param beans the message bean to verify
*/
public void checkMessageBean( MessageDrivenMetaData bean)
{}
/**
* Returns the context object reference for this strategy implementation.
*
* @return the client object using this algorithm implementation
*/
public StrategyContext getContext()
{
return context;
}
/*
*************************************************************************
*
* PRIVATE INSTANCE METHODS
*
*************************************************************************
*/
private boolean isRMIIIOPType(Class type)
{
/*
* Java Language to IDL Mapping
*
* ftp://ftp.omg.org/pub/docs/ptc/99-03-09.pdf
*
* A conforming RMI/IDL type is a Java type whose values may be
* transmitted across an RMI/IDL remote interface at run-time.
* A Java data type is a conforming RMI/IDL type if it is:
*
* - one of the Java primitive types (see Primitive Types on
* page 28-2).
* - a conforming remote interface (as defined in RMI/IDL
* Remote Interfaces on page 28-2).
* - a conforming value type (as defined in RMI/IDL Value Types
* on page 28-4).
* - an array of conforming RMI/IDL types (see RMI/IDL Arrays on
* page 28-5).
* - a conforming exception type (see RMI/IDL Exception Types on
* page 28-5).
* - a conforming CORBA object reference type (see CORBA Object
* Reference Types on page 28-6).
* - a conforming IDL entity type see IDL Entity Types on page
* 28-6).
*/
/*
* Primitive types.
*
* Spec 28.2.2
*/
if( type.isPrimitive() )
return true;
/*
* Conforming array.
*
* Spec 28.2.5
*/
if( type.isArray() )
return isRMIIIOPType(type.getComponentType());
/*
* Conforming CORBA reference type
*
* Spec 28.2.7
*/
if( org.omg.CORBA.Object.class.isAssignableFrom(type) )
return true;
/*
* Conforming IDL Entity type
*
* Spec 28.2.8
*/
if( org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(type) )
return true;
/*
* Conforming remote interface.
*
* Spec 28.2.3
*/
if( isRMIIDLRemoteInterface(type) )
return true;
/*
* Conforming exception.
*
* Spec 28.2.6
*/
if( isRMIIDLExceptionType(type) )
return true;
/*
* Conforming value type.
*
* Spec 28.2.4
*/
if( isRMIIDLValueType(type) )
return true;
return false;
}
private boolean isRMIIDLRemoteInterface( Class type )
{
/*
* If does not implement java.rmi.Remote, cannot be valid RMI-IDL
* remote interface.
*/
if( !java.rmi.Remote.class.isAssignableFrom(type) )
return false;
Iterator methodIterator = Arrays.asList(type.getMethods()).iterator();
while( methodIterator.hasNext() )
{
Method m = (Method)methodIterator.next();
/*
* All methods in the interface MUST throw
* java.rmi.RemoteException or its subclass.
*
* Spec 28.2.3 (2)
*/
if( !throwsRemoteException(m) )
return false;
/*
* All checked exception classes used in method declarations
* (other than java.rmi.RemoteException) MUST be conforming
* RMI/IDL exception types.
*
* Spec 28.2.3 (4)
*/
Iterator it = Arrays.asList(m.getExceptionTypes()).iterator();
while( it.hasNext() )
{
Class exception = (Class)it.next();
if( !isRMIIDLExceptionType(exception) )
return false;
}
}
/*
* The constant values defined in the interface MUST be
* compile-time types of RMI/IDL primitive types or String.
*
* Spec 28.2.3 (6)
*/
Iterator fieldIterator = Arrays.asList(type.getFields()).iterator();
while( fieldIterator.hasNext() )
{
Field f = (Field)fieldIterator.next();
if( f.getType().isPrimitive() )
continue;
if (f.getType().equals(java.lang.String.class))
continue;
return false;
}
return true;
}
private boolean isRMIIDLExceptionType( Class type )
{
/*
* A conforming RMI/IDL Exception class MUST be a checked
* exception class and MUST be a valid RMI/IDL value type.
*
* Spec 28.2.6
*/
if( !Throwable.class.isAssignableFrom(type) )
return false;
if (Error.class.isAssignableFrom(type))
return false;
// 28.3.4.4 (6) java.rmi.RemoteException and its subclasses, and unchecked
// exception classes, are assumed to be mapped to the implicit
// CORBA system exception, and are therefore not explicitly
// declared in OMG IDL.
//
// if (RuntimeException.class.isAssignableFrom(type))
// return false;
if (!isRMIIDLValueType(type))
return false;
return true;
}
protected boolean isRMIIDLValueType(Class type)
{
/*
* A value type MUST NOT either directly or indirectly implement the
* java.rmi.Remote interface.
*
* Spec 28.2.4 (4)
*/
if( java.rmi.Remote.class.isAssignableFrom(type) )
return false;
/*
* If class is a non-static inner class then its containing class must
* also be a conforming RMI/IDL value type.
*
* Spec 28.2.4 (3)
*/
if( type.getDeclaringClass() != null && !isStatic(type) )
{
if( !isRMIIDLValueType(type.getDeclaringClass()) )
return false;
}
return true;
}
private String getMatchingEJBHomeName(String homeName)
{
return "ejbHome" + homeName.substring(0,1).toUpperCase() +
homeName.substring(1);
}
private String getMatchingEJBCreateName(String createName)
{
return "ejb" + createName.substring(0,1).toUpperCase() +
createName.substring(1);
}
private String getMatchingEJBPostCreateName(String createName)
{
int createIdx = createName.indexOf("Create");
return "ejbPost" + createName.substring(createIdx>=0?createIdx:0);
}
/*
*************************************************************************
*
* STRING CONSTANTS
*
*************************************************************************
*/
/*
* Ejb-jar DTD
*/
public final static String BEAN_MANAGED_TX =
"Bean";
public final static String CONTAINER_MANAGED_TX =
"Container";
public final static String STATEFUL_SESSION =
"Stateful";
public final static String STATELESS_SESSION =
"Stateless";
/*
* method names
*/
private final static String EJB_FIND_BY_PRIMARY_KEY =
"ejbFindByPrimaryKey";
protected final static String EJB_CREATE_METHOD =
"ejbCreate";
protected final static String EJB_REMOVE_METHOD =
"ejbRemove";
private final static String EJB_POST_CREATE_METHOD =
"ejbPostCreate";
protected final static String CREATE_METHOD =
"create";
protected final static String EJB_HOME_METHOD =
"ejbHome";
protected final static String EJB_SELECT_METHOD =
"ejbSelect";
private final static String FINALIZE_METHOD =
"finalize";
private final static String REMOVE_METHOD =
"remove";
private final static String GET_HOME_HANDLE_METHOD =
"getHomeHandle";
private final static String GET_EJB_METADATA_METHOD =
"getEJBMetaData";
}
/*
vim:ts=3:sw=3:et
*/