1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.tomcat.util;
18
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.net.InetAddress;
22 import java.net.UnknownHostException;
23 import java.util.Hashtable;
24
25 import org.apache.juli.logging.Log;
26 import org.apache.juli.logging.LogFactory;
27 import org.apache.tomcat.util.res.StringManager;
28 import org.apache.tomcat.util.security.PermissionCheck;
29
30 /**
31  * Utils for introspection and reflection
32  */

33 public final class IntrospectionUtils {
34
35     private static final Log log = LogFactory.getLog(IntrospectionUtils.class);
36     private static final StringManager sm = StringManager.getManager(IntrospectionUtils.class);
37
38     /**
39      * Find a method with the right name If found, call the method ( if param is
40      * int or boolean we'll convert value to the right type before) - that means
41      * you can have setDebug(1).
42      * @param o The object to set a property on
43      * @param name The property name
44      * @param value The property value
45      * @return <code>true</code> if operation was successful
46      */

47     public static boolean setProperty(Object o, String name, String value) {
48         return setProperty(o,name,value,true);
49     }
50
51     @SuppressWarnings("null"// setPropertyMethodVoid is not null when used
52     public static boolean setProperty(Object o, String name, String value,
53             boolean invokeSetProperty) {
54         if (log.isDebugEnabled())
55             log.debug("IntrospectionUtils: setProperty(" +
56                     o.getClass() + " " + name + "=" + value + ")");
57
58         String setter = "set" + capitalize(name);
59
60         try {
61             Method methods[] = findMethods(o.getClass());
62             Method setPropertyMethodVoid = null;
63             Method setPropertyMethodBool = null;
64
65             // First, the ideal case - a setFoo( String ) method
66             for (int i = 0; i < methods.length; i++) {
67                 Class<?> paramT[] = methods[i].getParameterTypes();
68                 if (setter.equals(methods[i].getName()) && paramT.length == 1
69                         && "java.lang.String".equals(paramT[0].getName())) {
70
71                     methods[i].invoke(o, new Object[] { value });
72                     return true;
73                 }
74             }
75
76             // Try a setFoo ( int ) or ( boolean )
77             for (int i = 0; i < methods.length; i++) {
78                 boolean ok = true;
79                 if (setter.equals(methods[i].getName())
80                         && methods[i].getParameterTypes().length == 1) {
81
82                     // match - find the type and invoke it
83                     Class<?> paramType = methods[i].getParameterTypes()[0];
84                     Object params[] = new Object[1];
85
86                     // Try a setFoo ( int )
87                     if ("java.lang.Integer".equals(paramType.getName())
88                             || "int".equals(paramType.getName())) {
89                         try {
90                             params[0] = Integer.valueOf(value);
91                         } catch (NumberFormatException ex) {
92                             ok = false;
93                         }
94                     // Try a setFoo ( long )
95                     }else if ("java.lang.Long".equals(paramType.getName())
96                                 || "long".equals(paramType.getName())) {
97                             try {
98                                 params[0] = Long.valueOf(value);
99                             } catch (NumberFormatException ex) {
100                                 ok = false;
101                             }
102
103                         // Try a setFoo ( boolean )
104                     } else if ("java.lang.Boolean".equals(paramType.getName())
105                             || "boolean".equals(paramType.getName())) {
106                         params[0] = Boolean.valueOf(value);
107
108                         // Try a setFoo ( InetAddress )
109                     } else if ("java.net.InetAddress".equals(paramType
110                             .getName())) {
111                         try {
112                             params[0] = InetAddress.getByName(value);
113                         } catch (UnknownHostException exc) {
114                             if (log.isDebugEnabled())
115                                 log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
116                             ok = false;
117                         }
118
119                         // Unknown type
120                     } else {
121                         if (log.isDebugEnabled())
122                             log.debug("IntrospectionUtils: Unknown type " +
123                                     paramType.getName());
124                     }
125
126                     if (ok) {
127                         methods[i].invoke(o, params);
128                         return true;
129                     }
130                 }
131
132                 // save "setProperty" for later
133                 if ("setProperty".equals(methods[i].getName())) {
134                     if (methods[i].getReturnType()==Boolean.TYPE){
135                         setPropertyMethodBool = methods[i];
136                     }else {
137                         setPropertyMethodVoid = methods[i];
138                     }
139
140                 }
141             }
142
143             // Ok, no setXXX found, try a setProperty("name""value")
144             if (invokeSetProperty && (setPropertyMethodBool != null ||
145                     setPropertyMethodVoid != null)) {
146                 Object params[] = new Object[2];
147                 params[0] = name;
148                 params[1] = value;
149                 if (setPropertyMethodBool != null) {
150                     try {
151                         return ((Boolean) setPropertyMethodBool.invoke(o,
152                                 params)).booleanValue();
153                     }catch (IllegalArgumentException biae) {
154                         //the boolean method had the wrong
155                         //parameter types. lets try the other
156                         if (setPropertyMethodVoid!=null) {
157                             setPropertyMethodVoid.invoke(o, params);
158                             return true;
159                         }else {
160                             throw biae;
161                         }
162                     }
163                 } else {
164                     setPropertyMethodVoid.invoke(o, params);
165                     return true;
166                 }
167             }
168
169         } catch (IllegalArgumentException | SecurityException | IllegalAccessException e) {
170             log.warn(sm.getString("introspectionUtils.setPropertyError", name, value, o.getClass()), e);
171         } catch (InvocationTargetException e) {
172             ExceptionUtils.handleThrowable(e.getCause());
173             log.warn(sm.getString("introspectionUtils.setPropertyError", name, value, o.getClass()), e);
174         }
175         return false;
176     }
177
178     public static Object getProperty(Object o, String name) {
179         String getter = "get" + capitalize(name);
180         String isGetter = "is" + capitalize(name);
181
182         try {
183             Method methods[] = findMethods(o.getClass());
184             Method getPropertyMethod = null;
185
186             // First, the ideal case - a getFoo() method
187             for (int i = 0; i < methods.length; i++) {
188                 Class<?> paramT[] = methods[i].getParameterTypes();
189                 if (getter.equals(methods[i].getName()) && paramT.length == 0) {
190                     return methods[i].invoke(o, (Object[]) null);
191                 }
192                 if (isGetter.equals(methods[i].getName()) && paramT.length == 0) {
193                     return methods[i].invoke(o, (Object[]) null);
194                 }
195
196                 if ("getProperty".equals(methods[i].getName())) {
197                     getPropertyMethod = methods[i];
198                 }
199             }
200
201             // Ok, no setXXX found, try a getProperty("name")
202             if (getPropertyMethod != null) {
203                 Object params[] = new Object[1];
204                 params[0] = name;
205                 return getPropertyMethod.invoke(o, params);
206             }
207
208         } catch (IllegalArgumentException | SecurityException | IllegalAccessException e) {
209             log.warn(sm.getString("introspectionUtils.getPropertyError", name, o.getClass()), e);
210         } catch (InvocationTargetException e) {
211             if (e.getCause() instanceof NullPointerException) {
212                 // Assume the underlying object uses a storage to represent an unset property
213                 return null;
214             }
215             ExceptionUtils.handleThrowable(e.getCause());
216             log.warn(sm.getString("introspectionUtils.getPropertyError", name, o.getClass()), e);
217         }
218         return null;
219     }
220
221     /**
222      * Replace ${NAME} with the property value.
223      * @param value The value
224      * @param staticProp Replacement properties
225      * @param dynamicProp Replacement properties
226      * @return the replacement value
227      * @deprecated Use {@link #replaceProperties(String, Hashtable, PropertySource[], ClassLoader)}
228      */

229     @Deprecated
230     public static String replaceProperties(String value,
231             Hashtable<Object,Object> staticProp, PropertySource dynamicProp[]) {
232         return replaceProperties(value, staticProp, dynamicProp, null);
233     }
234
235     /**
236      * Replace ${NAME} with the property value.
237      * @param value The value
238      * @param staticProp Replacement properties
239      * @param dynamicProp Replacement properties
240      * @param classLoader Class loader associated with the code requesting the
241      *                    property
242      * @return the replacement value
243      */

244     public static String replaceProperties(String value,
245             Hashtable<Object,Object> staticProp, PropertySource dynamicProp[],
246             ClassLoader classLoader) {
247
248         if (value.indexOf('$') < 0) {
249             return value;
250         }
251         StringBuilder sb = new StringBuilder();
252         int prev = 0;
253         // assert value!=nil
254         int pos;
255         while ((pos = value.indexOf('$', prev)) >= 0) {
256             if (pos > 0) {
257                 sb.append(value.substring(prev, pos));
258             }
259             if (pos == (value.length() - 1)) {
260                 sb.append('$');
261                 prev = pos + 1;
262             } else if (value.charAt(pos + 1) != '{') {
263                 sb.append('$');
264                 prev = pos + 1; // XXX
265             } else {
266                 int endName = value.indexOf('}', pos);
267                 if (endName < 0) {
268                     sb.append(value.substring(pos));
269                     prev = value.length();
270                     continue;
271                 }
272                 String n = value.substring(pos + 2, endName);
273                 String v = null;
274                 if (staticProp != null) {
275                     v = (String) staticProp.get(n);
276                 }
277                 if (v == null && dynamicProp != null) {
278                     for (PropertySource propertySource : dynamicProp) {
279                         if (propertySource instanceof SecurePropertySource) {
280                             v = ((SecurePropertySource) propertySource).getProperty(n, classLoader);
281                         } else {
282                             v = propertySource.getProperty(n);
283                         }
284                         if (v != null) {
285                             break;
286                         }
287                     }
288                 }
289                 if (v == null)
290                     v = "${" + n + "}";
291
292                 sb.append(v);
293                 prev = endName + 1;
294             }
295         }
296         if (prev < value.length())
297             sb.append(value.substring(prev));
298         return sb.toString();
299     }
300
301     /**
302      * Reverse of Introspector.decapitalize.
303      * @param name The name
304      * @return the capitalized string
305      */

306     public static String capitalize(String name) {
307         if (name == null || name.length() == 0) {
308             return name;
309         }
310         char chars[] = name.toCharArray();
311         chars[0] = Character.toUpperCase(chars[0]);
312         return new String(chars);
313     }
314
315     // -------------------- other utils --------------------
316     public static void clear() {
317         objectMethods.clear();
318     }
319
320     private static final Hashtable<Class<?>,Method[]> objectMethods = new Hashtable<>();
321
322     public static Method[] findMethods(Class<?> c) {
323         Method methods[] = objectMethods.get(c);
324         if (methods != null)
325             return methods;
326
327         methods = c.getMethods();
328         objectMethods.put(c, methods);
329         return methods;
330     }
331
332     @SuppressWarnings("null"// params cannot be null when comparing lengths
333     public static Method findMethod(Class<?> c, String name,
334             Class<?> params[]) {
335         Method methods[] = findMethods(c);
336         for (int i = 0; i < methods.length; i++) {
337             if (methods[i].getName().equals(name)) {
338                 Class<?> methodParams[] = methods[i].getParameterTypes();
339                 if (params == null && methodParams.length == 0) {
340                     return methods[i];
341                 }
342                 if (params.length != methodParams.length) {
343                     continue;
344                 }
345                 boolean found = true;
346                 for (int j = 0; j < params.length; j++) {
347                     if (params[j] != methodParams[j]) {
348                         found = false;
349                         break;
350                     }
351                 }
352                 if (found) {
353                     return methods[i];
354                 }
355             }
356         }
357         return null;
358     }
359
360     public static Object callMethod1(Object target, String methodN,
361             Object param1, String typeParam1, ClassLoader cl) throws Exception {
362         if (target == null || methodN == null || param1 == null) {
363             throw new IllegalArgumentException(sm.getString("introspectionUtils.nullParameter"));
364         }
365         if (log.isDebugEnabled())
366             log.debug("IntrospectionUtils: callMethod1 " +
367                     target.getClass().getName() + " " +
368                     param1.getClass().getName() + " " + typeParam1);
369
370         Class<?> params[] = new Class[1];
371         if (typeParam1 == null)
372             params[0] = param1.getClass();
373         else
374             params[0] = cl.loadClass(typeParam1);
375         Method m = findMethod(target.getClass(), methodN, params);
376         if (m == null)
377             throw new NoSuchMethodException(target.getClass().getName() + " "
378                     + methodN);
379         try {
380             return m.invoke(target, new Object[] { param1 });
381         } catch (InvocationTargetException ie) {
382             ExceptionUtils.handleThrowable(ie.getCause());
383             throw ie;
384         }
385     }
386
387     public static Object callMethodN(Object target, String methodN,
388             Object params[], Class<?> typeParams[]) throws Exception {
389         Method m = null;
390         m = findMethod(target.getClass(), methodN, typeParams);
391         if (m == null) {
392             if (log.isDebugEnabled())
393                 log.debug("IntrospectionUtils: Can't find method " + methodN +
394                         " in " + target + " CLASS " + target.getClass());
395             return null;
396         }
397         try {
398             Object o = m.invoke(target, params);
399
400             if (log.isDebugEnabled()) {
401                 // debug
402                 StringBuilder sb = new StringBuilder();
403                 sb.append(target.getClass().getName()).append('.')
404                         .append(methodN).append("( ");
405                 for (int i = 0; i < params.length; i++) {
406                     if (i > 0)
407                         sb.append(", ");
408                     sb.append(params[i]);
409                 }
410                 sb.append(")");
411                 log.debug("IntrospectionUtils:" + sb.toString());
412             }
413             return o;
414         } catch (InvocationTargetException ie) {
415             ExceptionUtils.handleThrowable(ie.getCause());
416             throw ie;
417         }
418     }
419
420     public static Object convert(String object, Class<?> paramType) {
421         Object result = null;
422         if ("java.lang.String".equals(paramType.getName())) {
423             result = object;
424         } else if ("java.lang.Integer".equals(paramType.getName())
425                 || "int".equals(paramType.getName())) {
426             try {
427                 result = Integer.valueOf(object);
428             } catch (NumberFormatException ex) {
429             }
430             // Try a setFoo ( boolean )
431         } else if ("java.lang.Boolean".equals(paramType.getName())
432                 || "boolean".equals(paramType.getName())) {
433             result = Boolean.valueOf(object);
434
435             // Try a setFoo ( InetAddress )
436         } else if ("java.net.InetAddress".equals(paramType
437                 .getName())) {
438             try {
439                 result = InetAddress.getByName(object);
440             } catch (UnknownHostException exc) {
441                 if (log.isDebugEnabled())
442                     log.debug("IntrospectionUtils: Unable to resolve host name:" +
443                             object);
444             }
445
446             // Unknown type
447         } else {
448             if (log.isDebugEnabled())
449                 log.debug("IntrospectionUtils: Unknown type " +
450                         paramType.getName());
451         }
452         if (result == null) {
453             throw new IllegalArgumentException(sm.getString("introspectionUtils.conversionError", object, paramType.getName()));
454         }
455         return result;
456     }
457
458
459     /**
460      * Checks to see if the specified class is an instance of or assignable from
461      * the specified type. The class <code>clazz</code>, all its superclasses,
462      * interfaces and those superinterfaces are tested for a match against
463      * the type name <code>type</code>.
464      *
465      * This is similar to <code>instanceof</code> or {@link Class#isAssignableFrom}
466      * except that the target type will not be resolved into a Class
467      * object, which provides some security and memory benefits.
468      *
469      * @param clazz The class to test for a match.
470      * @param type The name of the type that <code>clazz</code> must be.
471      *
472      * @return <code>true</code> if the <code>clazz</code> tested is an
473      *         instance of the specified <code>type</code>,
474      *         <code>false</code> otherwise.
475      */

476     public static boolean isInstance(Class<?> clazz, String type) {
477         if (type.equals(clazz.getName())) {
478             return true;
479         }
480
481         Class<?>[] ifaces = clazz.getInterfaces();
482         for (Class<?> iface : ifaces) {
483             if (isInstance(iface, type)) {
484                 return true;
485             }
486         }
487
488         Class<?> superClazz = clazz.getSuperclass();
489         if (superClazz == null) {
490             return false;
491         } else {
492             return isInstance(superClazz, type);
493         }
494     }
495
496
497     // -------------------- Get property --------------------
498     // This provides a layer of abstraction
499
500     public static interface PropertySource {
501         public String getProperty(String key);
502     }
503
504
505     public static interface SecurePropertySource extends PropertySource {
506
507         /**
508          * Obtain a property value, checking that code associated with the
509          * provided class loader has permission to access the property. If the
510          * {@code classLoader} is {@code null} or if {@code classLoader} does
511          * not implement {@link PermissionCheck} then the property value will be
512          * looked up <b>without</b> a call to
513          * {@link PermissionCheck#check(java.security.Permission)}
514          *
515          * @param key           The key of the requested property
516          * @param classLoader   The class loader associated with the code that
517          *                      trigger the property lookup
518          * @return The property value or {@code nullif it could not be found
519          *         or if {@link PermissionCheck#check(java.security.Permission)}
520          *         fails
521          */

522         public String getProperty(String key, ClassLoader classLoader);
523     }
524 }
525