View Javadoc

1   /**********************************************************************
2    * BaseRemotingHandler.java
3    * created on 23.07.2006 by netseeker
4    * $Id$
5    * $Log$
6    * Revision 1.11  2007/11/17 10:57:00  netseeker
7    * *** empty log message ***
8    *
9    * Revision 1.10  2007/03/22 21:01:35  netseeker
10   * *** empty log message ***
11   *
12   * Revision 1.9  2006/12/02 19:10:36  netseeker
13   * adapted move of RemotingRequest to the main package
14   *
15   * Revision 1.8  2006/11/27 22:58:28  netseeker
16   * *** empty log message ***
17   *
18   * Revision 1.7  2006/11/12 20:34:44  netseeker
19   * *** empty log message ***
20   *
21   * Revision 1.6  2006/11/11 19:13:16  netseeker
22   * added security mechanism for remote reflection as well as for permitted adapters
23   *
24   * Revision 1.5  2006/11/06 08:50:30  netseeker
25   * fixed java 1.4 support
26   *
27   * Revision 1.4  2006/11/05 16:59:31  netseeker
28   * added support for remote reflection via usage of RemotingHandlers
29   *
30   * Revision 1.3  2006/07/27 20:35:15  netseeker
31   * *** empty log message ***
32   *
33   *
34   * ====================================================================
35   *
36   *  Copyright 2006 netseeker aka Michael Manske
37   *
38   *  Licensed under the Apache License, Version 2.0 (the "License");
39   *  you may not use this file except in compliance with the License.
40   *  You may obtain a copy of the License at
41   *
42   *      http://www.apache.org/licenses/LICENSE-2.0
43   *
44   *  Unless required by applicable law or agreed to in writing, software
45   *  distributed under the License is distributed on an "AS IS" BASIS,
46   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
47   *  See the License for the specific language governing permissions and
48   *  limitations under the License.
49   * ====================================================================
50   *
51   * This file is part of the EJOE framework.
52   * For more information on the author, please see
53   * <http://www.manskes.de/>.
54   *
55   *********************************************************************/
56  
57  package de.netseeker.ejoe.handler;
58  
59  import java.io.InputStream;
60  import java.security.AccessControlException;
61  import java.util.Collections;
62  import java.util.HashSet;
63  import java.util.Hashtable;
64  import java.util.Iterator;
65  import java.util.Map;
66  import java.util.Properties;
67  import java.util.Set;
68  import java.util.logging.Level;
69  import java.util.logging.Logger;
70  
71  import de.netseeker.ejoe.cache.ClassRegistry;
72  import de.netseeker.ejoe.cache.LRUMap;
73  import de.netseeker.ejoe.io.IOUtil;
74  import de.netseeker.ejoe.request.RemotingRequest;
75  
76  /***
77   * Base class for the family of reflection based ServerHandlers. Basically these ServerHandlers allow the usage of
78   * remote method invocations which the EJOE Sever handles using:
79   * <ul>
80   * <li>either standard java reflection with caching enhancements or</li>
81   * <li>assisted reflection with dynmically generated and cached proxy classes, which don't have to use reflection after
82   * the first invocation</li>
83   * </ul>
84   * 
85   * @author netseeker
86   * @since 0.3.9.1
87   */
88  public abstract class BaseRemotingHandler implements ServerHandler, ClassRegistry
89  {
90      private static final Logger logger             = Logger.getLogger( BaseRemotingHandler.class.getName() );
91  
92      /***
93       * cache for all primitive-wrapper combinations
94       */
95      private static Hashtable    primitiveToWrapper = new Hashtable();
96  
97      /***
98       * cache for all wrapper-primitive combinations
99       */
100     private static Hashtable    wrapperToPrimitive = new Hashtable();
101 
102     /***
103      * permission store containing package/class -names which are allowed or not allowed to be used in remote reflection
104      * calls
105      */
106     private static String[]     reflectionPermissions;
107 
108     /***
109      * all permitted class names will be stored in this cache for faster lookup
110      */
111     private static Set          permissionCache    = Collections.synchronizedSet( new HashSet() );
112 
113     /***
114      * pre-cache all known Wrapper-Primitive-Pairs as well as all known Primitive-Wrapper-Pairs
115      */
116 
117     protected Map               classCache         = new LRUMap();
118 
119     static
120     {
121         registerTypeMappings();
122         loadReflectionPermissions();
123     }
124 
125     private static void registerTypeMappings()
126     {
127         primitiveToWrapper.put( Boolean.TYPE, Boolean.class );
128         primitiveToWrapper.put( Byte.TYPE, Byte.class );
129         primitiveToWrapper.put( Short.TYPE, Short.class );
130         primitiveToWrapper.put( Character.TYPE, Character.class );
131         primitiveToWrapper.put( Integer.TYPE, Integer.class );
132         primitiveToWrapper.put( Long.TYPE, Long.class );
133         primitiveToWrapper.put( Float.TYPE, Float.class );
134         primitiveToWrapper.put( Double.TYPE, Double.class );
135         wrapperToPrimitive.put( Boolean.class, Boolean.TYPE );
136         wrapperToPrimitive.put( Byte.class, Byte.TYPE );
137         wrapperToPrimitive.put( Short.class, Short.TYPE );
138         wrapperToPrimitive.put( Character.class, Character.TYPE );
139         wrapperToPrimitive.put( Integer.class, Integer.TYPE );
140         wrapperToPrimitive.put( Long.class, Long.TYPE );
141         wrapperToPrimitive.put( Float.class, Float.TYPE );
142         wrapperToPrimitive.put( Double.class, Double.TYPE );
143     }
144 
145     private static void loadReflectionPermissions()
146     {
147         Properties props = new Properties();
148         InputStream in = null;
149         try
150         {
151             in = BaseRemotingHandler.class.getResourceAsStream( "/ejoe-reflection-conf.properties" );
152             props.load( in );
153         }
154         catch ( Exception e )
155         {
156             logger
157                     .log(
158                           Level.INFO,
159                           "No customized ejoe-reflection-conf.properties found on classpath! If you don't want to use"
160                                   + " the default reflection permissions (META-INF/ejoe-reflection-conf.properties) "
161                                   + " make sure you have placed a valid ejoe-reflection-conf.properties file in your classpath." );
162             try
163             {
164                 in = BaseRemotingHandler.class.getResourceAsStream( "/META-INF/ejoe-reflection-conf.properties" );
165                 props.load( in );
166             }
167             catch ( Exception ioe )
168             {
169                 logger.log( Level.SEVERE,
170                             "Reflection permissions could no be read from /META-INF/ejoe-reflection-conf.properties!!! "
171                                     + " No remote reflection will be available in EJServer!" );
172             }
173         }
174         finally
175         {
176             IOUtil.closeQuiet( in );
177         }
178 
179         if ( props != null )
180         {
181             String name = null;
182             reflectionPermissions = new String[props.size()];
183             int count = 0;
184 
185             for ( Iterator it = props.keySet().iterator(); it.hasNext(); )
186             {
187                 name = (String) it.next();
188                 reflectionPermissions[count] = name;
189                 count++;
190             }
191         }
192     }
193 
194     /*
195      * (non-Javadoc)
196      * 
197      * @see de.netseeker.ejoe.handler.ServerHandler#handle(java.lang.Object)
198      */
199     public Object handle( Object params ) throws Exception
200     {
201         RemotingRequest request = (RemotingRequest) params;
202 
203         if ( request.getClazz() == null )
204         {
205             throw new IllegalArgumentException( "Parameter \"targetClass\" not found in request!" );
206         }
207 
208         if ( request.getMethod() == null )
209         {
210             throw new IllegalArgumentException( "Parameter \"targetMethod\" not found in request!" );
211         }
212 
213         Class clazz = getClassByName( request.getClazz() );
214 
215         if ( clazz.isInterface() )
216         {
217             throw new IllegalArgumentException( "Parameter \"targetClass\" must not be an interface!" );
218         }
219         else if ( clazz.isPrimitive() )
220         {
221             throw new IllegalArgumentException( "Parameter \"targetClass\" must not be an primitive!" );
222         }
223 
224         return handle( clazz, request.getMethod(), request.getArgs() );
225     }
226 
227     /***
228      * Returns the appropiate wrapper type for the given primitive type, eg. Integer for int
229      * 
230      * @param primitive primitive java type
231      * @return appropiate java wrapper type
232      */
233     public static Class getWrapperForPrimitive( Class primitive )
234     {
235         return (Class) primitiveToWrapper.get( primitive );
236     }
237 
238     /***
239      * Returns the appropiate primitive type for the given wrapper type, eg. int for Integer
240      * 
241      * @param wrapper java wrapper type
242      * @return appropiate java primitive type
243      */
244     public static Class getPrimitiveForWrapper( Class wrapper )
245     {
246         return (Class) wrapperToPrimitive.get( wrapper );
247     }
248 
249     /***
250      * Either loads a class via reflection or (if already cached) from a Class-cache
251      * 
252      * @param cName complete name of the class with package names
253      * @return a Class
254      * @throws ClassNotFoundException
255      */
256     protected Class getClassByName( String cName ) throws ClassNotFoundException, AccessControlException
257     {
258         // first try to get the Class-object from cache
259         Class clazz = (Class) classCache.get( cName );
260 
261         // ok, never loaded this class before or it was removed from cache
262         if ( clazz == null )
263         {
264             if ( isReflectionPermitted( cName ) )
265             {
266                 clazz = Class.forName( cName );
267                 classCache.put( cName, clazz );
268             }
269             else
270             {
271                 throw new AccessControlException( "EJOE does not permit usage of \"" + cName
272                         + "\" for remote reflection calls!" );
273             }
274         }
275 
276         return clazz;
277     }
278 
279     private final boolean isReflectionPermitted( String className )
280     {
281         if ( permissionCache.contains( className ) ) return true;
282 
283         String entry = null, subName = className;
284         int index = -1;
285 
286         for ( int i = 0; i < reflectionPermissions.length; i++ )
287         {
288             entry = reflectionPermissions[i];
289             if ( entry.equals( className ) )
290             {
291                 permissionCache.add( className );
292                 return true;
293             }
294             if ( entry.endsWith( "*" ) )
295             {
296                 index = subName.lastIndexOf( '.' );
297                 while ( index != -1 )
298                 {
299                     subName = subName.substring( 0, index );
300                     if ( entry.startsWith( subName ) )
301                     {
302                         permissionCache.add( className );
303                         return true;
304                     }
305                     index = subName.lastIndexOf( '.' );
306                 }
307             }
308         }
309 
310         return false;
311     }
312 
313     /*
314      * (non-Javadoc)
315      * 
316      * @see de.netseeker.ejoe.handler.ClassRegistry#registerClassMapping(java.lang.String, java.lang.String)
317      */
318     public void registerClassMapping( String clazz, String alias )
319     {
320         if ( !classCache.containsKey( alias ) )
321         {
322             try
323             {
324                 Class theClass = Class.forName( clazz );
325                 classCache.put( alias, theClass );
326                 classCache.put( clazz, theClass );
327             }
328             catch ( ClassNotFoundException e )
329             {
330                 logger.log( Level.SEVERE, e.getMessage(), e );
331             }
332         }
333     }
334 
335     /*
336      * (non-Javadoc)
337      * 
338      * @see de.netseeker.ejoe.handler.ClassRegistry#deRegisterClassMapping(java.lang.String)
339      */
340     public boolean deRegisterClassMapping( String alias )
341     {
342         Class theClass = (Class) classCache.get( alias );
343         boolean removed = false;
344 
345         if ( theClass != null )
346         {
347             classCache.remove( alias );
348             classCache.remove( theClass.getName() );
349             removed = true;
350         }
351 
352         return removed;
353     }
354 
355     /***
356      * @param target
357      * @param method
358      * @param args
359      * @return
360      */
361     abstract Object handle( Class target, String method, Object[] args ) throws Exception;
362 }