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.

AbstractExtensionFinder.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. * Copyright 2015 Decebal Suiu
  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.pf4j.util.ClassUtils;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. /**
  27. * @author Decebal Suiu
  28. */
  29. public abstract class AbstractExtensionFinder implements ExtensionFinder, PluginStateListener {
  30. private static final Logger log = LoggerFactory.getLogger(AbstractExtensionFinder.class);
  31. protected PluginManager pluginManager;
  32. protected volatile Map<String, Set<String>> entries; // cache by pluginId
  33. public AbstractExtensionFinder(PluginManager pluginManager) {
  34. this.pluginManager = pluginManager;
  35. }
  36. public abstract Map<String, Set<String>> readPluginsStorages();
  37. public abstract Map<String, Set<String>> readClasspathStorages();
  38. @Override
  39. @SuppressWarnings("unchecked")
  40. public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
  41. log.debug("Finding extensions of extension point '{}'", type.getName());
  42. Map<String, Set<String>> entries = getEntries();
  43. List<ExtensionWrapper<T>> result = new ArrayList<>();
  44. // add extensions found in classpath and plugins
  45. for (String pluginId : entries.keySet()) {
  46. // classpath's extensions <=> pluginId = null
  47. List<ExtensionWrapper<T>> pluginExtensions = find(type, pluginId);
  48. result.addAll(pluginExtensions);
  49. }
  50. if (entries.isEmpty()) {
  51. log.debug("No extensions found for extension point '{}'", type.getName());
  52. } else {
  53. log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
  54. }
  55. // sort by "ordinal" property
  56. Collections.sort(result);
  57. return result;
  58. }
  59. @Override
  60. @SuppressWarnings("unchecked")
  61. public <T> List<ExtensionWrapper<T>> find(Class<T> type, String pluginId) {
  62. log.debug("Finding extensions of extension point '{}' for plugin '{}'", type.getName(), pluginId);
  63. List<ExtensionWrapper<T>> result = new ArrayList<>();
  64. // classpath's extensions <=> pluginId = null
  65. Set<String> classNames = findClassNames(pluginId);
  66. if (classNames == null || classNames.isEmpty()) {
  67. return result;
  68. }
  69. if (pluginId != null) {
  70. PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
  71. if (PluginState.STARTED != pluginWrapper.getPluginState()) {
  72. return result;
  73. }
  74. log.trace("Checking extensions from plugin '{}'", pluginId);
  75. } else {
  76. log.trace("Checking extensions from classpath");
  77. }
  78. ClassLoader classLoader = (pluginId != null) ? pluginManager.getPluginClassLoader(pluginId) : getClass().getClassLoader();
  79. for (String className : classNames) {
  80. try {
  81. log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
  82. Class<?> extensionClass = classLoader.loadClass(className);
  83. log.debug("Checking extension type '{}'", className);
  84. if (type.isAssignableFrom(extensionClass)) {
  85. ExtensionWrapper extensionWrapper = createExtensionWrapper(extensionClass);
  86. result.add(extensionWrapper);
  87. log.debug("Added extension '{}' with ordinal {}", className, extensionWrapper.getOrdinal());
  88. } else {
  89. log.trace("'{}' is not an extension for extension point '{}'", className, type.getName());
  90. if (RuntimeMode.DEVELOPMENT.equals(pluginManager.getRuntimeMode())) {
  91. checkDifferentClassLoaders(type, extensionClass);
  92. }
  93. }
  94. } catch (ClassNotFoundException e) {
  95. log.error(e.getMessage(), e);
  96. }
  97. }
  98. if (result.isEmpty()) {
  99. log.debug("No extensions found for extension point '{}'", type.getName());
  100. } else {
  101. log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
  102. }
  103. // sort by "ordinal" property
  104. Collections.sort(result);
  105. return result;
  106. }
  107. @Override
  108. public List<ExtensionWrapper> find(String pluginId) {
  109. log.debug("Finding extensions from plugin '{}'", pluginId);
  110. List<ExtensionWrapper> result = new ArrayList<>();
  111. Set<String> classNames = findClassNames(pluginId);
  112. if (classNames.isEmpty()) {
  113. return result;
  114. }
  115. if (pluginId != null) {
  116. PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
  117. if (PluginState.STARTED != pluginWrapper.getPluginState()) {
  118. return result;
  119. }
  120. log.trace("Checking extensions from plugin '{}'", pluginId);
  121. } else {
  122. log.trace("Checking extensions from classpath");
  123. }
  124. ClassLoader classLoader = (pluginId != null) ? pluginManager.getPluginClassLoader(pluginId) : getClass().getClassLoader();
  125. for (String className : classNames) {
  126. try {
  127. log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
  128. Class<?> extensionClass = classLoader.loadClass(className);
  129. ExtensionWrapper extensionWrapper = createExtensionWrapper(extensionClass);
  130. result.add(extensionWrapper);
  131. log.debug("Added extension '{}' with ordinal {}", className, extensionWrapper.getOrdinal());
  132. } catch (ClassNotFoundException e) {
  133. log.error(e.getMessage(), e);
  134. }
  135. }
  136. if (result.isEmpty()) {
  137. log.debug("No extensions found for plugin '{}'", pluginId);
  138. } else {
  139. log.debug("Found {} extensions for plugin '{}'", result.size(), pluginId);
  140. }
  141. // sort by "ordinal" property
  142. Collections.sort(result);
  143. return result;
  144. }
  145. @Override
  146. public Set<String> findClassNames(String pluginId) {
  147. return getEntries().get(pluginId);
  148. }
  149. @Override
  150. public void pluginStateChanged(PluginStateEvent event) {
  151. // TODO optimize (do only for some transitions)
  152. // clear cache
  153. entries = null;
  154. }
  155. protected void debugExtensions(Set<String> extensions) {
  156. if (log.isDebugEnabled()) {
  157. if (extensions.isEmpty()) {
  158. log.debug("No extensions found");
  159. } else {
  160. log.debug("Found possible {} extensions:", extensions.size());
  161. for (String extension : extensions) {
  162. log.debug(" " + extension);
  163. }
  164. }
  165. }
  166. }
  167. private Map<String, Set<String>> readStorages() {
  168. Map<String, Set<String>> result = new LinkedHashMap<>();
  169. result.putAll(readClasspathStorages());
  170. result.putAll(readPluginsStorages());
  171. return result;
  172. }
  173. private Map<String, Set<String>> getEntries() {
  174. if (entries == null) {
  175. entries = readStorages();
  176. }
  177. return entries;
  178. }
  179. private ExtensionWrapper createExtensionWrapper(Class<?> extensionClass) {
  180. ExtensionDescriptor descriptor = new ExtensionDescriptor();
  181. int ordinal = 0;
  182. if (extensionClass.isAnnotationPresent(Extension.class)) {
  183. ordinal = extensionClass.getAnnotation(Extension.class).ordinal();
  184. }
  185. descriptor.setOrdinal(ordinal);
  186. descriptor.setExtensionClass(extensionClass);
  187. ExtensionWrapper extensionWrapper = new ExtensionWrapper<>(descriptor);
  188. extensionWrapper.setExtensionFactory(pluginManager.getExtensionFactory());
  189. return extensionWrapper;
  190. }
  191. private void checkDifferentClassLoaders(Class<?> type, Class<?> extensionClass) {
  192. ClassLoader typeClassLoader = type.getClassLoader(); // class loader of extension point
  193. ClassLoader extensionClassLoader = extensionClass.getClassLoader();
  194. boolean match = ClassUtils.getAllInterfacesNames(extensionClass).contains(type.getSimpleName());
  195. if (match && !extensionClassLoader.equals(typeClassLoader)) {
  196. // in this scenario the method 'isAssignableFrom' returns only FALSE
  197. // see http://www.coderanch.com/t/557846/java/java/FWIW-FYI-isAssignableFrom-isInstance-differing
  198. log.error("Different class loaders: '{}' (E) and '{}' (EP)", extensionClassLoader, typeClassLoader);
  199. }
  200. }
  201. }