/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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. | |
*/ | |
package org.apache.uima.internal.util; | |
import java.net.URL; | |
import java.util.IdentityHashMap; | |
import java.util.Map; | |
/** | |
* Class Loader for loading localized messages | |
* See https://issues.apache.org/jira/browse/UIMA-1714 | |
* Delegates to other ClassLoaders, in the following order | |
* the class loader that loaded the 2nd previous caller | |
* the class loader that loaded the 3rd previous caller | |
* etc. | |
* and finally, the thread local context loader, if it exists UIMA-3692 | |
* Note: the caller of this method is presumed to be framework code | |
* that was, in turn, called to perform some logging or whatever, | |
* so we skip the 1st previous caller. | |
* | |
* Note that each of these class loaders will, in turn, delegate | |
* if they are set up to do so | |
* | |
* Note: if a caller's class loader is the same as the previously tried one, | |
* we skip it (simple speed optimization, and may avoid some kind of strange loop) | |
*/ | |
public class MsgLocalizationClassLoader { | |
/** | |
* this inner class only for purposes of getting access to the protected method | |
* to get the call stack | |
*/ | |
static class CallStack extends SecurityManager { | |
Class<?>[] getCallStack() { | |
return getClassContext(); | |
} | |
} | |
static final CallStack csi = new CallStack(); | |
static class CallClimbingClassLoader extends ClassLoader { | |
static final ThreadLocal<ClassLoader> originalTccl = new ThreadLocal<>(); | |
/* | |
* Try to load the class itself before delegate the class loading to its parent | |
*/ | |
@Override | |
protected synchronized Class<?> loadClass(String name, boolean resolve) | |
throws ClassNotFoundException { | |
// First, check if the class has already been loaded | |
Class<?> c = findLoadedClass(name); | |
if (c == null) { | |
// try to load class | |
c = findClass(name); // may throw; if so, don't delegate, delegation has already been done | |
} | |
if (resolve) { | |
resolveClass(c); | |
} | |
return c; | |
} | |
/** | |
* Called after findLoadedClass has returned null | |
* Delegates loading in specific order | |
*/ | |
@Override | |
protected Class<?> findClass(String name) throws ClassNotFoundException { | |
Map<ClassLoader,ClassLoader> alreadySearched = new IdentityHashMap<>(7); | |
// get the call stack | |
Class<?>[] cs = new CallStack().getCallStack(); | |
// start at the caller of the caller's class loader | |
// cs[0] is getClassContext | |
// cs[1] is getCallStack | |
// cs[2] is this method, find class | |
for (int i = 3; i < cs.length; i++) { | |
Class<?> callingClass = cs[i]; | |
ClassLoader cl = callingClass.getClassLoader(); | |
if (null == cl) { // means system class loader | |
cl = ClassLoader.getSystemClassLoader(); | |
} | |
if (null != alreadySearched.get(cl)) { | |
continue; | |
} | |
alreadySearched.put(cl, cl); | |
Class<?> c = null; | |
try { | |
c = cl.loadClass(name); // include delegation | |
return c; | |
} catch (ClassNotFoundException e) { | |
// leave c == null | |
} | |
} | |
// UIMA-3692, UIMA-4793 try the thread context class loader | |
// if not found, will return class not found exception | |
try { | |
ClassLoader cl = originalTccl.get(); | |
if (cl != null) { | |
return cl.loadClass(name); | |
} | |
} catch (ClassNotFoundException e) {} | |
// last try: the current thread context class loader | |
return Thread.currentThread().getContextClassLoader().loadClass(name); | |
} | |
@Override | |
public URL getResource(String name) { | |
Map<ClassLoader,ClassLoader> alreadySearched = new IdentityHashMap<>(7); | |
// get the call stack | |
Class<?>[] cs = new CallStack().getCallStack(); | |
// start at the caller of the caller's class loader | |
// cs[0] is getClassContext | |
// cs[1] is getCallStack | |
// cs[2] is this method, find class | |
for (int i = 3; i < cs.length; i++) { | |
Class<?> callingClass = cs[i]; | |
ClassLoader cl = callingClass.getClassLoader(); | |
if (null == cl) { // means system class loader | |
cl = ClassLoader.getSystemClassLoader(); | |
} | |
if (null != alreadySearched.get(cl)) { | |
continue; | |
} | |
alreadySearched.put(cl, cl); | |
URL c = cl.getResource(name); // include delegation | |
if (null != c) { | |
return c; | |
} | |
} | |
// UIMA-3692, UIMA-4793 try the thread context class loader | |
// if not found, will return class not found exception | |
ClassLoader cl = originalTccl.get(); | |
if (cl != null) { | |
URL c = cl.getResource(name); | |
if (null != c) { | |
return c; | |
} | |
} | |
return Thread.currentThread().getContextClassLoader().getResource(name); | |
} | |
} | |
private static final CallClimbingClassLoader MSG_LOCALIZATION_CLASS_LOADER = | |
new CallClimbingClassLoader(); | |
public static URL getResource(String name) { | |
return MSG_LOCALIZATION_CLASS_LOADER.getResource(name); | |
} | |
public static ClassLoader getMsgLocalizationClassLoader() { | |
return MSG_LOCALIZATION_CLASS_LOADER; | |
} | |
public static Class<?> loadClass(String name) throws ClassNotFoundException { | |
return MSG_LOCALIZATION_CLASS_LOADER.loadClass(name); | |
} | |
} |