You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginClassLoader.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * Copyright (C) 2012-present the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.pf4j;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.net.URL;
  22. import java.net.URLClassLoader;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.Enumeration;
  27. import java.util.List;
  28. import java.util.Objects;
  29. /**
  30. * One instance of this class should be created for every available plug-in.
  31. * It's responsible for loading classes and resources from the plug-in.
  32. * <p>
  33. * By default, this {@link ClassLoader} is a Parent Last ClassLoader - it loads the classes from the plugin's jars
  34. * before delegating to the parent class loader.
  35. * Use {@link #classLoadingStrategy} to change the loading strategy.
  36. *
  37. * @author Decebal Suiu
  38. */
  39. public class PluginClassLoader extends URLClassLoader {
  40. private static final Logger log = LoggerFactory.getLogger(PluginClassLoader.class);
  41. private static final String JAVA_PACKAGE_PREFIX = "java.";
  42. private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j.";
  43. private final PluginManager pluginManager;
  44. private final PluginDescriptor pluginDescriptor;
  45. private final ClassLoadingStrategy classLoadingStrategy;
  46. public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) {
  47. this(pluginManager, pluginDescriptor, parent, ClassLoadingStrategy.PDA);
  48. }
  49. /**
  50. * Creates a new {@link PluginClassLoader} for the given plugin using parent first strategy.
  51. *
  52. * @deprecated Replaced by {@link #PluginClassLoader(PluginManager, PluginDescriptor, ClassLoader, ClassLoadingStrategy)}.
  53. * If {@code parentFirst} is {@code true}, indicates that the parent {@link ClassLoader} should be consulted
  54. * before trying to load a class through this loader.
  55. */
  56. @Deprecated
  57. public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, boolean parentFirst) {
  58. this(pluginManager, pluginDescriptor, parent, parentFirst ? ClassLoadingStrategy.APD : ClassLoadingStrategy.PDA);
  59. }
  60. /**
  61. * Creates a new {@link PluginClassLoader} for the given plugin using the specified class loading strategy.
  62. *
  63. * @param pluginManager the plugin manager
  64. * @param pluginDescriptor the plugin descriptor
  65. * @param parent the parent class loader
  66. * @param classLoadingStrategy the strategy to use for loading classes and resources
  67. */
  68. public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, ClassLoadingStrategy classLoadingStrategy) {
  69. super(new URL[0], parent);
  70. this.pluginManager = pluginManager;
  71. this.pluginDescriptor = pluginDescriptor;
  72. this.classLoadingStrategy = classLoadingStrategy;
  73. }
  74. /**
  75. * Adds the specified URL to the search path for classes and resources.
  76. *
  77. * @param url the URL to be added to the search path of URLs
  78. */
  79. @Override
  80. public void addURL(URL url) {
  81. log.debug("Add '{}'", url);
  82. super.addURL(url);
  83. }
  84. /**
  85. * Adds the specified file to the search path for classes and resources.
  86. *
  87. * @param file the file to be added to the search path of URLs
  88. */
  89. public void addFile(File file) {
  90. try {
  91. addURL(file.getCanonicalFile().toURI().toURL());
  92. } catch (IOException e) {
  93. // throw new RuntimeException(e);
  94. log.error(e.getMessage(), e);
  95. }
  96. }
  97. /**
  98. * Loads the class with the specified name.
  99. * <p>
  100. * By default, it uses a child first delegation model rather than the standard parent first.
  101. * If the requested class cannot be found in this class loader, the parent class loader will be consulted
  102. * via the standard {@link ClassLoader#loadClass(String)} mechanism.
  103. * Use {@link #classLoadingStrategy} to change the loading strategy.
  104. *
  105. * @param className the name of the class
  106. * @return the loaded class
  107. */
  108. @Override
  109. public Class<?> loadClass(String className) throws ClassNotFoundException {
  110. synchronized (getClassLoadingLock(className)) {
  111. // first check whether it's a system class, delegate to the system loader
  112. if (className.startsWith(JAVA_PACKAGE_PREFIX)) {
  113. return findSystemClass(className);
  114. }
  115. // if the class is part of the plugin engine use parent class loader
  116. if (className.startsWith(PLUGIN_PACKAGE_PREFIX) && !className.startsWith("org.pf4j.demo") && !className.startsWith("org.pf4j.test")) {
  117. // log.trace("Delegate the loading of PF4J class '{}' to parent", className);
  118. return getParent().loadClass(className);
  119. }
  120. log.trace("Received request to load class '{}'", className);
  121. // second check whether it's already been loaded
  122. Class<?> loadedClass = findLoadedClass(className);
  123. if (loadedClass != null) {
  124. log.trace("Found loaded class '{}'", className);
  125. return loadedClass;
  126. }
  127. for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) {
  128. Class<?> c = null;
  129. try {
  130. switch (classLoadingSource) {
  131. case APPLICATION:
  132. c = super.loadClass(className);
  133. break;
  134. case PLUGIN:
  135. c = findClass(className);
  136. break;
  137. case DEPENDENCIES:
  138. c = loadClassFromDependencies(className);
  139. break;
  140. }
  141. } catch (ClassNotFoundException ignored) {}
  142. if (c != null) {
  143. log.trace("Found class '{}' in {} classpath", className, classLoadingSource);
  144. return c;
  145. } else {
  146. log.trace("Couldn't find class '{}' in {} classpath", className, classLoadingSource);
  147. }
  148. }
  149. throw new ClassNotFoundException(className);
  150. }
  151. }
  152. /**
  153. * Loads the named resource from this plugin.
  154. * <p>
  155. * By default, this implementation checks the plugin's classpath first then delegates to the parent.
  156. * Use {@link #classLoadingStrategy} to change the loading strategy.
  157. *
  158. * @param name the name of the resource.
  159. * @return the URL to the resource, {@code null} if the resource was not found.
  160. */
  161. @Override
  162. public URL getResource(String name) {
  163. log.trace("Received request to load resource '{}'", name);
  164. for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) {
  165. URL url = null;
  166. switch (classLoadingSource) {
  167. case APPLICATION:
  168. url = super.getResource(name);
  169. break;
  170. case PLUGIN:
  171. url = findResource(name);
  172. break;
  173. case DEPENDENCIES:
  174. url = findResourceFromDependencies(name);
  175. break;
  176. }
  177. if (url != null) {
  178. log.trace("Found resource '{}' in {} classpath", name, classLoadingSource);
  179. return url;
  180. } else {
  181. log.trace("Couldn't find resource '{}' in {}", name, classLoadingSource);
  182. }
  183. }
  184. return null;
  185. }
  186. @Override
  187. public Enumeration<URL> getResources(String name) throws IOException {
  188. List<URL> resources = new ArrayList<>();
  189. log.trace("Received request to load resources '{}'", name);
  190. for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) {
  191. switch (classLoadingSource) {
  192. case APPLICATION:
  193. if (getParent() != null) {
  194. resources.addAll(Collections.list(getParent().getResources(name)));
  195. }
  196. break;
  197. case PLUGIN:
  198. resources.addAll(Collections.list(findResources(name)));
  199. break;
  200. case DEPENDENCIES:
  201. resources.addAll(findResourcesFromDependencies(name));
  202. break;
  203. }
  204. }
  205. return Collections.enumeration(resources);
  206. }
  207. /**
  208. * Loads the class with the specified name from the dependencies of the plugin.
  209. *
  210. * @param className the name of the class
  211. * @return the loaded class
  212. */
  213. protected Class<?> loadClassFromDependencies(String className) {
  214. log.trace("Search in dependencies for class '{}'", className);
  215. List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
  216. for (PluginDependency dependency : dependencies) {
  217. ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
  218. // If the dependency is marked as optional, its class loader might not be available.
  219. if (classLoader == null && dependency.isOptional()) {
  220. continue;
  221. }
  222. try {
  223. return classLoader.loadClass(className);
  224. } catch (ClassNotFoundException e) {
  225. // try next dependency
  226. }
  227. }
  228. return null;
  229. }
  230. /**
  231. * Finds the resource with the given name in the dependencies of the plugin.
  232. *
  233. * @param name the name of the resource
  234. * @return the URL to the resource, {@code null} if the resource was not found
  235. */
  236. protected URL findResourceFromDependencies(String name) {
  237. log.trace("Search in dependencies for resource '{}'", name);
  238. List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
  239. for (PluginDependency dependency : dependencies) {
  240. PluginClassLoader classLoader = (PluginClassLoader) pluginManager.getPluginClassLoader(dependency.getPluginId());
  241. // If the dependency is marked as optional, its class loader might not be available.
  242. if (classLoader == null && dependency.isOptional()) {
  243. continue;
  244. }
  245. URL url = classLoader.findResource(name);
  246. if (Objects.nonNull(url)) {
  247. return url;
  248. }
  249. }
  250. return null;
  251. }
  252. /**
  253. * Finds all resources with the given name in the dependencies of the plugin.
  254. *
  255. * @param name the name of the resource
  256. * @return an enumeration of {@link URL} objects for the resource
  257. * @throws IOException if I/O errors occur
  258. */
  259. protected Collection<URL> findResourcesFromDependencies(String name) throws IOException {
  260. log.trace("Search in dependencies for resources '{}'", name);
  261. List<URL> results = new ArrayList<>();
  262. List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
  263. for (PluginDependency dependency : dependencies) {
  264. PluginClassLoader classLoader = (PluginClassLoader) pluginManager.getPluginClassLoader(dependency.getPluginId());
  265. // If the dependency is marked as optional, its class loader might not be available.
  266. if (classLoader == null && dependency.isOptional()) {
  267. continue;
  268. }
  269. results.addAll(Collections.list(classLoader.findResources(name)));
  270. }
  271. return results;
  272. }
  273. }