001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.lang.util; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024import java.io.InputStream; 025import java.lang.annotation.Annotation; 026import java.lang.reflect.Constructor; 027import java.lang.reflect.Method; 028import java.util.ArrayList; 029import java.util.HashMap; 030import java.util.List; 031 032 033/** 034 * Utility method library used to conveniently interact with <code>Class</code>es, such as acquiring them from the 035 * application <code>ClassLoader</code>s and instantiating Objects from them. 036 * 037 * @since 0.1 038 */ 039public final class ClassUtils { 040 041 /** 042 * Private internal log instance. 043 */ 044 private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class); 045 046 private static final ThreadLocal<ClassLoader> ADDITIONAL_CLASS_LOADER = new ThreadLocal<>(); 047 048 /** 049 * SHIRO-767: add a map to mapping primitive data type 050 */ 051 private static final HashMap<String, Class<?>> PRIM_CLASSES 052 = new HashMap<>(8, 1.0F); 053 054 static { 055 PRIM_CLASSES.put("boolean", boolean.class); 056 PRIM_CLASSES.put("byte", byte.class); 057 PRIM_CLASSES.put("char", char.class); 058 PRIM_CLASSES.put("short", short.class); 059 PRIM_CLASSES.put("int", int.class); 060 PRIM_CLASSES.put("long", long.class); 061 PRIM_CLASSES.put("float", float.class); 062 PRIM_CLASSES.put("double", double.class); 063 PRIM_CLASSES.put("void", void.class); 064 } 065 066 /** 067 * @since 1.0 068 */ 069 private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { 070 @Override 071 protected ClassLoader doGetClassLoader() throws Throwable { 072 return Thread.currentThread().getContextClassLoader(); 073 } 074 }; 075 076 /** 077 * @since 1.0 078 */ 079 private static final ClassLoaderAccessor CLASS_LANG_CL_ACCESSOR = new ExceptionIgnoringAccessor() { 080 @Override 081 protected ClassLoader doGetClassLoader() throws Throwable { 082 return ClassUtils.class.getClassLoader(); 083 } 084 }; 085 086 /** 087 * @since 2.0.4 088 */ 089 private static final ClassLoaderAccessor ADDITIONAL_CL_ACCESSOR = new ExceptionIgnoringAccessor() { 090 @Override 091 protected ClassLoader doGetClassLoader() throws Throwable { 092 ClassLoader cl = ADDITIONAL_CLASS_LOADER.get(); 093 return cl != null ? cl : ClassUtils.class.getClassLoader(); 094 } 095 }; 096 097 /** 098 * @since 1.0 099 */ 100 private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { 101 @Override 102 protected ClassLoader doGetClassLoader() throws Throwable { 103 return ClassLoader.getSystemClassLoader(); 104 } 105 }; 106 107 private ClassUtils() { 108 109 } 110 111 /** 112 * Returns the specified resource by checking the current thread's 113 * {@link Thread#getContextClassLoader() context class loader}, then the 114 * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application 115 * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order, using 116 * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}. 117 * 118 * @param name the name of the resource to acquire from the classloader(s). 119 * @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any 120 * of the three mentioned ClassLoaders. 121 * @since 0.9 122 */ 123 public static InputStream getResourceAsStream(String name) { 124 125 InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name); 126 127 if (is == null) { 128 if (LOGGER.isTraceEnabled()) { 129 LOGGER.trace("Resource [" + name + "] was not found via the thread context ClassLoader. Trying the " 130 + "current ClassLoader..."); 131 } 132 is = CLASS_LANG_CL_ACCESSOR.getResourceStream(name); 133 } 134 135 if (is == null) { 136 if (LOGGER.isTraceEnabled()) { 137 LOGGER.trace("Resource [" + name + "] was not found via the org.apache.shiro.lang ClassLoader. Trying the " 138 + "additionally set ClassLoader..."); 139 } 140 is = ADDITIONAL_CL_ACCESSOR.getResourceStream(name); 141 } 142 143 if (is == null) { 144 if (LOGGER.isTraceEnabled()) { 145 LOGGER.trace("Resource [" + name + "] was not found via the current class loader. Trying the " 146 + "system/application ClassLoader..."); 147 } 148 is = SYSTEM_CL_ACCESSOR.getResourceStream(name); 149 } 150 151 if (is == null && LOGGER.isTraceEnabled()) { 152 LOGGER.trace("Resource [" + name + "] was not found via the thread context, current, or " 153 + "system/application ClassLoaders. All heuristics have been exhausted. Returning null."); 154 } 155 156 return is; 157 } 158 159 /** 160 * Attempts to load the specified class name from the current thread's 161 * {@link Thread#getContextClassLoader() context class loader}, then the 162 * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application 163 * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order. If any of them cannot locate 164 * the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of 165 * the JRE's <code>ClassNotFoundException</code>. 166 * 167 * @param fqcn the fully qualified class name to load 168 * @return the located class 169 * @throws UnknownClassException if the class cannot be found. 170 */ 171 @SuppressWarnings("unchecked") 172 public static <T> Class<T> forName(String fqcn) throws UnknownClassException { 173 Class<?> clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); 174 175 if (clazz == null) { 176 if (LOGGER.isTraceEnabled()) { 177 LOGGER.trace("Unable to load class named [" + fqcn 178 + "] from the thread context ClassLoader. Trying the current ClassLoader..."); 179 } 180 clazz = CLASS_LANG_CL_ACCESSOR.loadClass(fqcn); 181 } 182 183 if (clazz == null) { 184 if (LOGGER.isTraceEnabled()) { 185 LOGGER.trace("Unable to load class named [" + fqcn 186 + "] from the org.apache.shiro.lang ClassLoader. Trying the additionally set ClassLoader..."); 187 } 188 clazz = ADDITIONAL_CL_ACCESSOR.loadClass(fqcn); 189 } 190 191 if (clazz == null) { 192 if (LOGGER.isTraceEnabled()) { 193 LOGGER.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " 194 + "Trying the system/application ClassLoader..."); 195 } 196 clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn); 197 } 198 199 if (clazz == null) { 200 //SHIRO-767: support for getting primitive data type,such as int,double... 201 clazz = PRIM_CLASSES.get(fqcn); 202 } 203 204 if (clazz == null) { 205 String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " 206 + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found."; 207 throw new UnknownClassException(msg); 208 } 209 210 return (Class<T>) clazz; 211 } 212 213 public static boolean isAvailable(String fullyQualifiedClassName) { 214 try { 215 forName(fullyQualifiedClassName); 216 return true; 217 } catch (UnknownClassException e) { 218 return false; 219 } 220 } 221 222 public static Object newInstance(String fqcn) { 223 return newInstance(forName(fqcn)); 224 } 225 226 public static Object newInstance(String fqcn, Object... args) { 227 return newInstance(forName(fqcn), args); 228 } 229 230 public static Object newInstance(Class<?> clazz) { 231 if (clazz == null) { 232 String msg = "Class method parameter cannot be null."; 233 throw new IllegalArgumentException(msg); 234 } 235 try { 236 return clazz.getDeclaredConstructor().newInstance(); 237 } catch (Exception e) { 238 throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e); 239 } 240 } 241 242 public static Object newInstance(Class<?> clazz, Object... args) { 243 var argTypes = new Class<?>[args.length]; 244 for (int i = 0; i < args.length; i++) { 245 argTypes[i] = args[i].getClass(); 246 } 247 Constructor<?> ctor = getConstructor(clazz, argTypes); 248 return instantiate(ctor, args); 249 } 250 251 public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... argTypes) { 252 try { 253 return clazz.getConstructor(argTypes); 254 } catch (NoSuchMethodException e) { 255 throw new IllegalStateException(e); 256 } 257 } 258 259 public static Object instantiate(Constructor<?> ctor, Object... args) { 260 try { 261 return ctor.newInstance(args); 262 } catch (Exception e) { 263 String msg = "Unable to instantiate Permission instance with constructor [" + ctor + "]"; 264 throw new InstantiationException(msg, e); 265 } 266 } 267 268 /** 269 * @param type 270 * @param annotation 271 * @return 272 * @since 1.3 273 */ 274 public static List<Method> getAnnotatedMethods(final Class<?> type, final Class<? extends Annotation> annotation) { 275 final List<Method> methods = new ArrayList<>(); 276 Class<?> clazz = type; 277 while (!Object.class.equals(clazz)) { 278 Method[] currentClassMethods = clazz.getDeclaredMethods(); 279 for (final Method method : currentClassMethods) { 280 if (annotation == null || method.isAnnotationPresent(annotation)) { 281 methods.add(method); 282 } 283 } 284 // move to the upper class in the hierarchy in search for more methods 285 clazz = clazz.getSuperclass(); 286 } 287 return methods; 288 } 289 290 /** 291 * Sets additional ClassLoader for {@link #getResourceAsStream(String)} and {@link #forName(String)} to use 292 * It is used in addition to the thread context class loader and the system class loader. 293 294 * @param classLoader class loader to use 295 * @since 2.0.4 296 */ 297 public static void setAdditionalClassLoader(ClassLoader classLoader) { 298 ADDITIONAL_CLASS_LOADER.set(classLoader); 299 } 300 301 /** 302 * Removes the additional ClassLoader set by {@link #setAdditionalClassLoader(ClassLoader)}. 303 * This must be called to avoid memory leaks. 304 * 305 * @since 2.0.4 306 */ 307 public static void removeAdditionalClassLoader() { 308 ADDITIONAL_CLASS_LOADER.remove(); 309 } 310 311 /** 312 * @since 1.0 313 */ 314 private interface ClassLoaderAccessor { 315 Class<?> loadClass(String fqcn); 316 317 InputStream getResourceStream(String name); 318 } 319 320 /** 321 * @since 1.0 322 */ 323 private abstract static class ExceptionIgnoringAccessor implements ClassLoaderAccessor { 324 325 public Class<?> loadClass(String fqcn) { 326 Class<?> clazz = null; 327 ClassLoader cl = getClassLoader(); 328 if (cl != null) { 329 try { 330 //SHIRO-767: Use Class.forName instead of cl.loadClass(), as byte arrays would fail otherwise. 331 clazz = Class.forName(fqcn, false, cl); 332 } catch (ClassNotFoundException e) { 333 if (LOGGER.isTraceEnabled()) { 334 LOGGER.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]"); 335 } 336 } 337 } 338 return clazz; 339 } 340 341 public InputStream getResourceStream(String name) { 342 InputStream is = null; 343 ClassLoader cl = getClassLoader(); 344 if (cl != null) { 345 is = cl.getResourceAsStream(name); 346 } 347 return is; 348 } 349 350 protected final ClassLoader getClassLoader() { 351 try { 352 return doGetClassLoader(); 353 } catch (Throwable t) { 354 if (LOGGER.isDebugEnabled()) { 355 LOGGER.debug("Unable to acquire ClassLoader.", t); 356 } 357 } 358 return null; 359 } 360 361 protected abstract ClassLoader doGetClassLoader() throws Throwable; 362 } 363}