/*********************************************************************
* BaseRemotingHandler.java
* created on 23.07.2006 by netseeker
* $Id$
* $Log$
* Revision 1.11 2007/11/17 10:57:00 netseeker
* *** empty log message ***
*
* Revision 1.10 2007/03/22 21:01:35 netseeker
* *** empty log message ***
*
* Revision 1.9 2006/12/02 19:10:36 netseeker
* adapted move of RemotingRequest to the main package
*
* Revision 1.8 2006/11/27 22:58:28 netseeker
* *** empty log message ***
*
* Revision 1.7 2006/11/12 20:34:44 netseeker
* *** empty log message ***
*
* Revision 1.6 2006/11/11 19:13:16 netseeker
* added security mechanism for remote reflection as well as for permitted adapters
*
* Revision 1.5 2006/11/06 08:50:30 netseeker
* fixed java 1.4 support
*
* Revision 1.4 2006/11/05 16:59:31 netseeker
* added support for remote reflection via usage of RemotingHandlers
*
* Revision 1.3 2006/07/27 20:35:15 netseeker
* *** empty log message ***
*
*
* ====================================================================
*
* Copyright 2006 netseeker aka Michael Manske
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This file is part of the EJOE framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.handler;
import java.io.InputStream;
import java.security.AccessControlException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.netseeker.ejoe.cache.ClassRegistry;
import de.netseeker.ejoe.cache.LRUMap;
import de.netseeker.ejoe.io.IOUtil;
import de.netseeker.ejoe.request.RemotingRequest;
/**
* Base class for the family of reflection based ServerHandlers. Basically these ServerHandlers allow the usage of
* remote method invocations which the EJOE Sever handles using:
* <ul>
* <li>either standard java reflection with caching enhancements or</li>
* <li>assisted reflection with dynmically generated and cached proxy classes, which don't have to use reflection after
* the first invocation</li>
* </ul>
*
* @author netseeker
* @since 0.3.9.1
*/
public abstract class BaseRemotingHandler implements ServerHandler, ClassRegistry
{
private static final Logger logger = Logger.getLogger( BaseRemotingHandler.class.getName() );
/**
* cache for all primitive-wrapper combinations
*/
private static Hashtable primitiveToWrapper = new Hashtable();
/**
* cache for all wrapper-primitive combinations
*/
private static Hashtable wrapperToPrimitive = new Hashtable();
/**
* permission store containing package/class -names which are allowed or not allowed to be used in remote reflection
* calls
*/
private static String[] reflectionPermissions;
/**
* all permitted class names will be stored in this cache for faster lookup
*/
private static Set permissionCache = Collections.synchronizedSet( new HashSet() );
/**
* pre-cache all known Wrapper-Primitive-Pairs as well as all known Primitive-Wrapper-Pairs
*/
protected Map classCache = new LRUMap();
static
{
registerTypeMappings();
loadReflectionPermissions();
}
private static void registerTypeMappings()
{
primitiveToWrapper.put( Boolean.TYPE, Boolean.class );
primitiveToWrapper.put( Byte.TYPE, Byte.class );
primitiveToWrapper.put( Short.TYPE, Short.class );
primitiveToWrapper.put( Character.TYPE, Character.class );
primitiveToWrapper.put( Integer.TYPE, Integer.class );
primitiveToWrapper.put( Long.TYPE, Long.class );
primitiveToWrapper.put( Float.TYPE, Float.class );
primitiveToWrapper.put( Double.TYPE, Double.class );
wrapperToPrimitive.put( Boolean.class, Boolean.TYPE );
wrapperToPrimitive.put( Byte.class, Byte.TYPE );
wrapperToPrimitive.put( Short.class, Short.TYPE );
wrapperToPrimitive.put( Character.class, Character.TYPE );
wrapperToPrimitive.put( Integer.class, Integer.TYPE );
wrapperToPrimitive.put( Long.class, Long.TYPE );
wrapperToPrimitive.put( Float.class, Float.TYPE );
wrapperToPrimitive.put( Double.class, Double.TYPE );
}
private static void loadReflectionPermissions()
{
Properties props = new Properties();
InputStream in = null;
try
{
in = BaseRemotingHandler.class.getResourceAsStream( "/ejoe-reflection-conf.properties" );
props.load( in );
}
catch ( Exception e )
{
logger
.log(
Level.INFO,
"No customized ejoe-reflection-conf.properties found on classpath! If you don't want to use"
+ " the default reflection permissions (META-INF/ejoe-reflection-conf.properties) "
+ " make sure you have placed a valid ejoe-reflection-conf.properties file in your classpath." );
try
{
in = BaseRemotingHandler.class.getResourceAsStream( "/META-INF/ejoe-reflection-conf.properties" );
props.load( in );
}
catch ( Exception ioe )
{
logger.log( Level.SEVERE,
"Reflection permissions could no be read from /META-INF/ejoe-reflection-conf.properties!!! "
+ " No remote reflection will be available in EJServer!" );
}
}
finally
{
IOUtil.closeQuiet( in );
}
if ( props != null )
{
String name = null;
reflectionPermissions = new String[props.size()];
int count = 0;
for ( Iterator it = props.keySet().iterator(); it.hasNext(); )
{
name = (String) it.next();
reflectionPermissions[count] = name;
count++;
}
}
}
/*
* (non-Javadoc)
*
* @see de.netseeker.ejoe.handler.ServerHandler#handle(java.lang.Object)
*/
public Object handle( Object params ) throws Exception
{
RemotingRequest request = (RemotingRequest) params;
if ( request.getClazz() == null )
{
throw new IllegalArgumentException( "Parameter \"targetClass\" not found in request!" );
}
if ( request.getMethod() == null )
{
throw new IllegalArgumentException( "Parameter \"targetMethod\" not found in request!" );
}
Class clazz = getClassByName( request.getClazz() );
if ( clazz.isInterface() )
{
throw new IllegalArgumentException( "Parameter \"targetClass\" must not be an interface!" );
}
else if ( clazz.isPrimitive() )
{
throw new IllegalArgumentException( "Parameter \"targetClass\" must not be an primitive!" );
}
return handle( clazz, request.getMethod(), request.getArgs() );
}
/**
* Returns the appropiate wrapper type for the given primitive type, eg. Integer for int
*
* @param primitive primitive java type
* @return appropiate java wrapper type
*/
public static Class getWrapperForPrimitive( Class primitive )
{
return (Class) primitiveToWrapper.get( primitive );
}
/**
* Returns the appropiate primitive type for the given wrapper type, eg. int for Integer
*
* @param wrapper java wrapper type
* @return appropiate java primitive type
*/
public static Class getPrimitiveForWrapper( Class wrapper )
{
return (Class) wrapperToPrimitive.get( wrapper );
}
/**
* Either loads a class via reflection or (if already cached) from a Class-cache
*
* @param cName complete name of the class with package names
* @return a Class
* @throws ClassNotFoundException
*/
protected Class getClassByName( String cName ) throws ClassNotFoundException, AccessControlException
{
// first try to get the Class-object from cache
Class clazz = (Class) classCache.get( cName );
// ok, never loaded this class before or it was removed from cache
if ( clazz == null )
{
if ( isReflectionPermitted( cName ) )
{
clazz = Class.forName( cName );
classCache.put( cName, clazz );
}
else
{
throw new AccessControlException( "EJOE does not permit usage of \"" + cName
+ "\" for remote reflection calls!" );
}
}
return clazz;
}
private final boolean isReflectionPermitted( String className )
{
if ( permissionCache.contains( className ) ) return true;
String entry = null, subName = className;
int index = -1;
for ( int i = 0; i < reflectionPermissions.length; i++ )
{
entry = reflectionPermissions[i];
if ( entry.equals( className ) )
{
permissionCache.add( className );
return true;
}
if ( entry.endsWith( "*" ) )
{
index = subName.lastIndexOf( '.' );
while ( index != -1 )
{
subName = subName.substring( 0, index );
if ( entry.startsWith( subName ) )
{
permissionCache.add( className );
return true;
}
index = subName.lastIndexOf( '.' );
}
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see de.netseeker.ejoe.handler.ClassRegistry#registerClassMapping(java.lang.String, java.lang.String)
*/
public void registerClassMapping( String clazz, String alias )
{
if ( !classCache.containsKey( alias ) )
{
try
{
Class theClass = Class.forName( clazz );
classCache.put( alias, theClass );
classCache.put( clazz, theClass );
}
catch ( ClassNotFoundException e )
{
logger.log( Level.SEVERE, e.getMessage(), e );
}
}
}
/*
* (non-Javadoc)
*
* @see de.netseeker.ejoe.handler.ClassRegistry#deRegisterClassMapping(java.lang.String)
*/
public boolean deRegisterClassMapping( String alias )
{
Class theClass = (Class) classCache.get( alias );
boolean removed = false;
if ( theClass != null )
{
classCache.remove( alias );
classCache.remove( theClass.getName() );
removed = true;
}
return removed;
}
/**
* @param target
* @param method
* @param args
* @return
*/
abstract Object handle( Class target, String method, Object[] args ) throws Exception;
}