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.

AbstractPluginManager.java 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  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.pf4j.util.StringUtils;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;
  20. import java.io.Closeable;
  21. import java.io.IOException;
  22. import java.nio.file.Files;
  23. import java.nio.file.Path;
  24. import java.nio.file.Paths;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Set;
  33. import java.util.stream.Collectors;
  34. /**
  35. * This class implements the boilerplate plugin code that any {@link PluginManager}
  36. * implementation would have to support.
  37. * It helps cut the noise out of the subclass that handles plugin management.
  38. *
  39. * <p>This class is not thread-safe.
  40. *
  41. * @author Decebal Suiu
  42. */
  43. public abstract class AbstractPluginManager implements PluginManager {
  44. private static final Logger log = LoggerFactory.getLogger(AbstractPluginManager.class);
  45. public static final String PLUGINS_DIR_PROPERTY_NAME = "pf4j.pluginsDir";
  46. public static final String MODE_PROPERTY_NAME = "pf4j.mode";
  47. public static final String DEFAULT_PLUGINS_DIR = "plugins";
  48. public static final String DEVELOPMENT_PLUGINS_DIR = "../plugins";
  49. protected final List<Path> pluginsRoots = new ArrayList<>();
  50. protected ExtensionFinder extensionFinder;
  51. protected PluginDescriptorFinder pluginDescriptorFinder;
  52. /**
  53. * A map of plugins this manager is responsible for (the key is the 'pluginId').
  54. */
  55. protected Map<String, PluginWrapper> plugins;
  56. /**
  57. * A map of plugin class loaders (the key is the 'pluginId').
  58. */
  59. protected Map<String, ClassLoader> pluginClassLoaders;
  60. /**
  61. * A list with unresolved plugins (unresolved dependency).
  62. */
  63. protected List<PluginWrapper> unresolvedPlugins;
  64. /**
  65. * A list with all resolved plugins (resolved dependency).
  66. */
  67. protected List<PluginWrapper> resolvedPlugins;
  68. /**
  69. * A list with started plugins.
  70. */
  71. protected List<PluginWrapper> startedPlugins;
  72. /**
  73. * The registered {@link PluginStateListener}s.
  74. */
  75. protected List<PluginStateListener> pluginStateListeners;
  76. /**
  77. * Cache value for the runtime mode.
  78. * No need to re-read it because it wont change at runtime.
  79. */
  80. protected RuntimeMode runtimeMode;
  81. /**
  82. * The system version used for comparisons to the plugin requires attribute.
  83. */
  84. protected String systemVersion = "0.0.0";
  85. protected PluginRepository pluginRepository;
  86. protected PluginFactory pluginFactory;
  87. protected ExtensionFactory extensionFactory;
  88. protected PluginStatusProvider pluginStatusProvider;
  89. protected DependencyResolver dependencyResolver;
  90. protected PluginLoader pluginLoader;
  91. protected boolean exactVersionAllowed = false;
  92. protected VersionManager versionManager;
  93. /**
  94. * The plugins roots are supplied as comma-separated list by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
  95. */
  96. protected AbstractPluginManager() {
  97. initialize();
  98. }
  99. /**
  100. * Constructs {@code AbstractPluginManager} with the given plugins roots.
  101. *
  102. * @param pluginsRoots the roots to search for plugins
  103. */
  104. protected AbstractPluginManager(Path... pluginsRoots) {
  105. this(Arrays.asList(pluginsRoots));
  106. }
  107. /**
  108. * Constructs {@code AbstractPluginManager} with the given plugins roots.
  109. *
  110. * @param pluginsRoots the roots to search for plugins
  111. */
  112. protected AbstractPluginManager(List<Path> pluginsRoots) {
  113. this.pluginsRoots.addAll(pluginsRoots);
  114. initialize();
  115. }
  116. @Override
  117. public void setSystemVersion(String version) {
  118. systemVersion = version;
  119. }
  120. @Override
  121. public String getSystemVersion() {
  122. return systemVersion;
  123. }
  124. /**
  125. * Returns a copy of plugins.
  126. */
  127. @Override
  128. public List<PluginWrapper> getPlugins() {
  129. return new ArrayList<>(plugins.values());
  130. }
  131. /**
  132. * Returns a copy of plugins with that state.
  133. */
  134. @Override
  135. public List<PluginWrapper> getPlugins(PluginState pluginState) {
  136. List<PluginWrapper> plugins = new ArrayList<>();
  137. for (PluginWrapper plugin : getPlugins()) {
  138. if (pluginState.equals(plugin.getPluginState())) {
  139. plugins.add(plugin);
  140. }
  141. }
  142. return plugins;
  143. }
  144. @Override
  145. public List<PluginWrapper> getResolvedPlugins() {
  146. return resolvedPlugins;
  147. }
  148. @Override
  149. public List<PluginWrapper> getUnresolvedPlugins() {
  150. return unresolvedPlugins;
  151. }
  152. @Override
  153. public List<PluginWrapper> getStartedPlugins() {
  154. return startedPlugins;
  155. }
  156. @Override
  157. public PluginWrapper getPlugin(String pluginId) {
  158. return plugins.get(pluginId);
  159. }
  160. @Override
  161. public String loadPlugin(Path pluginPath) {
  162. if ((pluginPath == null) || Files.notExists(pluginPath)) {
  163. throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginPath));
  164. }
  165. log.debug("Loading plugin from '{}'", pluginPath);
  166. PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
  167. // try to resolve the loaded plugin together with other possible plugins that depend on this plugin
  168. resolvePlugins();
  169. return pluginWrapper.getDescriptor().getPluginId();
  170. }
  171. /**
  172. * Load plugins.
  173. */
  174. @Override
  175. public void loadPlugins() {
  176. log.debug("Lookup plugins in '{}'", pluginsRoots);
  177. // check for plugins roots
  178. if (pluginsRoots.isEmpty()) {
  179. log.warn("No plugins roots configured");
  180. return;
  181. }
  182. pluginsRoots.forEach(path -> {
  183. if (Files.notExists(path) || !Files.isDirectory(path)) {
  184. log.warn("No '{}' root", path);
  185. }
  186. });
  187. // get all plugin paths from repository
  188. List<Path> pluginPaths = pluginRepository.getPluginPaths();
  189. // check for no plugins
  190. if (pluginPaths.isEmpty()) {
  191. log.info("No plugins");
  192. return;
  193. }
  194. log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
  195. // load plugins from plugin paths
  196. for (Path pluginPath : pluginPaths) {
  197. try {
  198. loadPluginFromPath(pluginPath);
  199. } catch (PluginRuntimeException e) {
  200. log.error(e.getMessage(), e);
  201. }
  202. }
  203. // resolve plugins
  204. try {
  205. resolvePlugins();
  206. } catch (PluginRuntimeException e) {
  207. log.error(e.getMessage(), e);
  208. }
  209. }
  210. /**
  211. * Unload all plugins
  212. */
  213. @Override
  214. public void unloadPlugins() {
  215. // wrap resolvedPlugins in new list because of concurrent modification
  216. for (PluginWrapper pluginWrapper : new ArrayList<>(resolvedPlugins)) {
  217. unloadPlugin(pluginWrapper.getPluginId());
  218. }
  219. }
  220. /**
  221. * Unload the specified plugin and it's dependents.
  222. */
  223. @Override
  224. public boolean unloadPlugin(String pluginId) {
  225. return unloadPlugin(pluginId, true);
  226. }
  227. protected boolean unloadPlugin(String pluginId, boolean unloadDependents) {
  228. try {
  229. if (unloadDependents) {
  230. List<String> dependents = dependencyResolver.getDependents(pluginId);
  231. while (!dependents.isEmpty()) {
  232. String dependent = dependents.remove(0);
  233. unloadPlugin(dependent, false);
  234. dependents.addAll(0, dependencyResolver.getDependents(dependent));
  235. }
  236. }
  237. PluginWrapper pluginWrapper = getPlugin(pluginId);
  238. PluginState pluginState;
  239. try {
  240. pluginState = stopPlugin(pluginId, false);
  241. if (PluginState.STARTED == pluginState) {
  242. return false;
  243. }
  244. log.info("Unload plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()));
  245. } catch (Exception e) {
  246. if (pluginWrapper == null) {
  247. return false;
  248. }
  249. pluginState = PluginState.FAILED;
  250. }
  251. // remove the plugin
  252. plugins.remove(pluginId);
  253. getResolvedPlugins().remove(pluginWrapper);
  254. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  255. // remove the classloader
  256. Map<String, ClassLoader> pluginClassLoaders = getPluginClassLoaders();
  257. if (pluginClassLoaders.containsKey(pluginId)) {
  258. ClassLoader classLoader = pluginClassLoaders.remove(pluginId);
  259. if (classLoader instanceof Closeable) {
  260. try {
  261. ((Closeable) classLoader).close();
  262. } catch (IOException e) {
  263. throw new PluginRuntimeException(e, "Cannot close classloader");
  264. }
  265. }
  266. }
  267. return true;
  268. } catch (IllegalArgumentException e) {
  269. // ignore not found exceptions because this method is recursive
  270. }
  271. return false;
  272. }
  273. @Override
  274. public boolean deletePlugin(String pluginId) {
  275. checkPluginId(pluginId);
  276. PluginWrapper pluginWrapper = getPlugin(pluginId);
  277. // stop the plugin if it's started
  278. PluginState pluginState = stopPlugin(pluginId);
  279. if (PluginState.STARTED == pluginState) {
  280. log.error("Failed to stop plugin '{}' on delete", pluginId);
  281. return false;
  282. }
  283. // get an instance of plugin before the plugin is unloaded
  284. // for reason see https://github.com/pf4j/pf4j/issues/309
  285. Plugin plugin = pluginWrapper.getPlugin();
  286. if (!unloadPlugin(pluginId)) {
  287. log.error("Failed to unload plugin '{}' on delete", pluginId);
  288. return false;
  289. }
  290. // notify the plugin as it's deleted
  291. plugin.delete();
  292. Path pluginPath = pluginWrapper.getPluginPath();
  293. return pluginRepository.deletePluginPath(pluginPath);
  294. }
  295. /**
  296. * Start all active plugins.
  297. */
  298. @Override
  299. public void startPlugins() {
  300. for (PluginWrapper pluginWrapper : resolvedPlugins) {
  301. PluginState pluginState = pluginWrapper.getPluginState();
  302. if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
  303. try {
  304. log.info("Start plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()));
  305. pluginWrapper.getPlugin().start();
  306. pluginWrapper.setPluginState(PluginState.STARTED);
  307. pluginWrapper.setFailedException(null);
  308. startedPlugins.add(pluginWrapper);
  309. } catch (Exception | LinkageError e) {
  310. pluginWrapper.setPluginState(PluginState.FAILED);
  311. pluginWrapper.setFailedException(e);
  312. log.error("Unable to start plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()), e);
  313. } finally {
  314. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Start the specified plugin and its dependencies.
  321. */
  322. @Override
  323. public PluginState startPlugin(String pluginId) {
  324. checkPluginId(pluginId);
  325. PluginWrapper pluginWrapper = getPlugin(pluginId);
  326. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  327. PluginState pluginState = pluginWrapper.getPluginState();
  328. if (PluginState.STARTED == pluginState) {
  329. log.debug("Already started plugin '{}'", getPluginLabel(pluginDescriptor));
  330. return PluginState.STARTED;
  331. }
  332. if (!resolvedPlugins.contains(pluginWrapper)) {
  333. log.warn("Cannot start an unresolved plugin '{}'", getPluginLabel(pluginDescriptor));
  334. return pluginState;
  335. }
  336. if (PluginState.DISABLED == pluginState) {
  337. // automatically enable plugin on manual plugin start
  338. if (!enablePlugin(pluginId)) {
  339. return pluginState;
  340. }
  341. }
  342. for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
  343. // start dependency only if it marked as required (non optional) or if it optional and loaded
  344. if (!dependency.isOptional() || plugins.containsKey(dependency.getPluginId())) {
  345. startPlugin(dependency.getPluginId());
  346. }
  347. }
  348. log.info("Start plugin '{}'", getPluginLabel(pluginDescriptor));
  349. pluginWrapper.getPlugin().start();
  350. pluginWrapper.setPluginState(PluginState.STARTED);
  351. startedPlugins.add(pluginWrapper);
  352. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  353. return pluginWrapper.getPluginState();
  354. }
  355. /**
  356. * Stop all active plugins.
  357. */
  358. @Override
  359. public void stopPlugins() {
  360. // stop started plugins in reverse order
  361. Collections.reverse(startedPlugins);
  362. Iterator<PluginWrapper> itr = startedPlugins.iterator();
  363. while (itr.hasNext()) {
  364. PluginWrapper pluginWrapper = itr.next();
  365. PluginState pluginState = pluginWrapper.getPluginState();
  366. if (PluginState.STARTED == pluginState) {
  367. try {
  368. log.info("Stop plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()));
  369. pluginWrapper.getPlugin().stop();
  370. pluginWrapper.setPluginState(PluginState.STOPPED);
  371. itr.remove();
  372. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  373. } catch (PluginRuntimeException e) {
  374. log.error(e.getMessage(), e);
  375. }
  376. }
  377. }
  378. }
  379. /**
  380. * Stop the specified plugin and it's dependents.
  381. */
  382. @Override
  383. public PluginState stopPlugin(String pluginId) {
  384. return stopPlugin(pluginId, true);
  385. }
  386. protected PluginState stopPlugin(String pluginId, boolean stopDependents) {
  387. checkPluginId(pluginId);
  388. PluginWrapper pluginWrapper = getPlugin(pluginId);
  389. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  390. PluginState pluginState = pluginWrapper.getPluginState();
  391. if (PluginState.STOPPED == pluginState) {
  392. log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor));
  393. return PluginState.STOPPED;
  394. }
  395. // test for disabled plugin
  396. if (PluginState.DISABLED == pluginState) {
  397. // do nothing
  398. return pluginState;
  399. }
  400. if (stopDependents) {
  401. List<String> dependents = dependencyResolver.getDependents(pluginId);
  402. while (!dependents.isEmpty()) {
  403. String dependent = dependents.remove(0);
  404. stopPlugin(dependent, false);
  405. dependents.addAll(0, dependencyResolver.getDependents(dependent));
  406. }
  407. }
  408. log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor));
  409. pluginWrapper.getPlugin().stop();
  410. pluginWrapper.setPluginState(PluginState.STOPPED);
  411. startedPlugins.remove(pluginWrapper);
  412. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  413. return pluginWrapper.getPluginState();
  414. }
  415. protected void checkPluginId(String pluginId) {
  416. if (!plugins.containsKey(pluginId)) {
  417. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  418. }
  419. }
  420. @Override
  421. public boolean disablePlugin(String pluginId) {
  422. checkPluginId(pluginId);
  423. PluginWrapper pluginWrapper = getPlugin(pluginId);
  424. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  425. PluginState pluginState = pluginWrapper.getPluginState();
  426. if (PluginState.DISABLED == pluginState) {
  427. log.debug("Already disabled plugin '{}'", getPluginLabel(pluginDescriptor));
  428. return true;
  429. }
  430. if (PluginState.STOPPED == stopPlugin(pluginId)) {
  431. pluginWrapper.setPluginState(PluginState.DISABLED);
  432. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
  433. pluginStatusProvider.disablePlugin(pluginId);
  434. log.info("Disabled plugin '{}'", getPluginLabel(pluginDescriptor));
  435. return true;
  436. }
  437. return false;
  438. }
  439. @Override
  440. public boolean enablePlugin(String pluginId) {
  441. checkPluginId(pluginId);
  442. PluginWrapper pluginWrapper = getPlugin(pluginId);
  443. if (!isPluginValid(pluginWrapper)) {
  444. log.warn("Plugin '{}' can not be enabled", getPluginLabel(pluginWrapper.getDescriptor()));
  445. return false;
  446. }
  447. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  448. PluginState pluginState = pluginWrapper.getPluginState();
  449. if (PluginState.DISABLED != pluginState) {
  450. log.debug("Plugin '{}' is not disabled", getPluginLabel(pluginDescriptor));
  451. return true;
  452. }
  453. pluginStatusProvider.enablePlugin(pluginId);
  454. pluginWrapper.setPluginState(PluginState.CREATED);
  455. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  456. log.info("Enabled plugin '{}'", getPluginLabel(pluginDescriptor));
  457. return true;
  458. }
  459. /**
  460. * Get the {@link ClassLoader} for plugin.
  461. */
  462. @Override
  463. public ClassLoader getPluginClassLoader(String pluginId) {
  464. return pluginClassLoaders.get(pluginId);
  465. }
  466. @SuppressWarnings("rawtypes")
  467. @Override
  468. public List<Class<?>> getExtensionClasses(String pluginId) {
  469. List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
  470. List<Class<?>> extensionClasses = new ArrayList<>(extensionsWrapper.size());
  471. for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
  472. Class<?> c = extensionWrapper.getDescriptor().extensionClass;
  473. extensionClasses.add(c);
  474. }
  475. return extensionClasses;
  476. }
  477. @Override
  478. public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type) {
  479. return getExtensionClasses(extensionFinder.find(type));
  480. }
  481. @Override
  482. public <T> List<Class<? extends T>> getExtensionClasses(Class<T> type, String pluginId) {
  483. return getExtensionClasses(extensionFinder.find(type, pluginId));
  484. }
  485. @Override
  486. public <T> List<T> getExtensions(Class<T> type) {
  487. return getExtensions(extensionFinder.find(type));
  488. }
  489. @Override
  490. public <T> List<T> getExtensions(Class<T> type, String pluginId) {
  491. return getExtensions(extensionFinder.find(type, pluginId));
  492. }
  493. @Override
  494. @SuppressWarnings("unchecked")
  495. public List getExtensions(String pluginId) {
  496. List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
  497. List extensions = new ArrayList<>(extensionsWrapper.size());
  498. for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
  499. try {
  500. extensions.add(extensionWrapper.getExtension());
  501. } catch (PluginRuntimeException e) {
  502. log.error("Cannot retrieve extension", e);
  503. }
  504. }
  505. return extensions;
  506. }
  507. @Override
  508. public Set<String> getExtensionClassNames(String pluginId) {
  509. return extensionFinder.findClassNames(pluginId);
  510. }
  511. @Override
  512. public ExtensionFactory getExtensionFactory() {
  513. return extensionFactory;
  514. }
  515. public PluginLoader getPluginLoader() {
  516. return pluginLoader;
  517. }
  518. @Override
  519. public Path getPluginsRoot() {
  520. return pluginsRoots.stream()
  521. .findFirst()
  522. .orElseThrow(() -> new IllegalStateException("pluginsRoots have not been initialized, yet."));
  523. }
  524. public List<Path> getPluginsRoots() {
  525. return Collections.unmodifiableList(pluginsRoots);
  526. }
  527. @Override
  528. public RuntimeMode getRuntimeMode() {
  529. if (runtimeMode == null) {
  530. // retrieves the runtime mode from system
  531. String modeAsString = System.getProperty(MODE_PROPERTY_NAME, RuntimeMode.DEPLOYMENT.toString());
  532. runtimeMode = RuntimeMode.byName(modeAsString);
  533. }
  534. return runtimeMode;
  535. }
  536. @Override
  537. public PluginWrapper whichPlugin(Class<?> clazz) {
  538. ClassLoader classLoader = clazz.getClassLoader();
  539. for (PluginWrapper plugin : resolvedPlugins) {
  540. if (plugin.getPluginClassLoader() == classLoader) {
  541. return plugin;
  542. }
  543. }
  544. return null;
  545. }
  546. @Override
  547. public synchronized void addPluginStateListener(PluginStateListener listener) {
  548. pluginStateListeners.add(listener);
  549. }
  550. @Override
  551. public synchronized void removePluginStateListener(PluginStateListener listener) {
  552. pluginStateListeners.remove(listener);
  553. }
  554. public String getVersion() {
  555. return Pf4jInfo.VERSION;
  556. }
  557. protected abstract PluginRepository createPluginRepository();
  558. protected abstract PluginFactory createPluginFactory();
  559. protected abstract ExtensionFactory createExtensionFactory();
  560. protected abstract PluginDescriptorFinder createPluginDescriptorFinder();
  561. protected abstract ExtensionFinder createExtensionFinder();
  562. protected abstract PluginStatusProvider createPluginStatusProvider();
  563. protected abstract PluginLoader createPluginLoader();
  564. protected abstract VersionManager createVersionManager();
  565. protected PluginDescriptorFinder getPluginDescriptorFinder() {
  566. return pluginDescriptorFinder;
  567. }
  568. protected PluginFactory getPluginFactory() {
  569. return pluginFactory;
  570. }
  571. protected Map<String, ClassLoader> getPluginClassLoaders() {
  572. return pluginClassLoaders;
  573. }
  574. protected void initialize() {
  575. plugins = new HashMap<>();
  576. pluginClassLoaders = new HashMap<>();
  577. unresolvedPlugins = new ArrayList<>();
  578. resolvedPlugins = new ArrayList<>();
  579. startedPlugins = new ArrayList<>();
  580. pluginStateListeners = new ArrayList<>();
  581. if (pluginsRoots.isEmpty()) {
  582. pluginsRoots.addAll(createPluginsRoot());
  583. }
  584. pluginRepository = createPluginRepository();
  585. pluginFactory = createPluginFactory();
  586. extensionFactory = createExtensionFactory();
  587. pluginDescriptorFinder = createPluginDescriptorFinder();
  588. extensionFinder = createExtensionFinder();
  589. pluginStatusProvider = createPluginStatusProvider();
  590. pluginLoader = createPluginLoader();
  591. versionManager = createVersionManager();
  592. dependencyResolver = new DependencyResolver(versionManager);
  593. }
  594. /**
  595. * Add the possibility to override the plugins roots.
  596. * If a {@link #PLUGINS_DIR_PROPERTY_NAME} system property is defined than this method returns that roots.
  597. * If {@link #getRuntimeMode()} returns {@link RuntimeMode#DEVELOPMENT} than {@link #DEVELOPMENT_PLUGINS_DIR}
  598. * is returned else this method returns {@link #DEFAULT_PLUGINS_DIR}.
  599. *
  600. * @return the plugins root
  601. */
  602. protected List<Path> createPluginsRoot() {
  603. String pluginsDir = System.getProperty(PLUGINS_DIR_PROPERTY_NAME);
  604. if (pluginsDir != null && !pluginsDir.isEmpty()) {
  605. return Arrays.stream(pluginsDir.split(","))
  606. .map(String::trim)
  607. .map(Paths::get)
  608. .collect(Collectors.toList());
  609. }
  610. pluginsDir = isDevelopment() ? DEVELOPMENT_PLUGINS_DIR : DEFAULT_PLUGINS_DIR;
  611. return Collections.singletonList(Paths.get(pluginsDir));
  612. }
  613. /**
  614. * Check if this plugin is valid (satisfies "requires" param) for a given system version.
  615. *
  616. * @param pluginWrapper the plugin to check
  617. * @return true if plugin satisfies the "requires" or if requires was left blank
  618. */
  619. protected boolean isPluginValid(PluginWrapper pluginWrapper) {
  620. String requires = pluginWrapper.getDescriptor().getRequires().trim();
  621. if (!isExactVersionAllowed() && requires.matches("^\\d+\\.\\d+\\.\\d+$")) {
  622. // If exact versions are not allowed in requires, rewrite to >= expression
  623. requires = ">=" + requires;
  624. }
  625. if (systemVersion.equals("0.0.0") || versionManager.checkVersionConstraint(systemVersion, requires)) {
  626. return true;
  627. }
  628. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  629. log.warn("Plugin '{}' requires a minimum system version of {}, and you have {}",
  630. getPluginLabel(pluginDescriptor),
  631. requires,
  632. getSystemVersion());
  633. return false;
  634. }
  635. protected boolean isPluginDisabled(String pluginId) {
  636. return pluginStatusProvider.isPluginDisabled(pluginId);
  637. }
  638. protected void resolvePlugins() {
  639. // retrieves the plugins descriptors
  640. List<PluginDescriptor> descriptors = new ArrayList<>();
  641. for (PluginWrapper plugin : plugins.values()) {
  642. descriptors.add(plugin.getDescriptor());
  643. }
  644. DependencyResolver.Result result = dependencyResolver.resolve(descriptors);
  645. if (result.hasCyclicDependency()) {
  646. throw new DependencyResolver.CyclicDependencyException();
  647. }
  648. List<String> notFoundDependencies = result.getNotFoundDependencies();
  649. if (!notFoundDependencies.isEmpty()) {
  650. throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies);
  651. }
  652. List<DependencyResolver.WrongDependencyVersion> wrongVersionDependencies = result.getWrongVersionDependencies();
  653. if (!wrongVersionDependencies.isEmpty()) {
  654. throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies);
  655. }
  656. List<String> sortedPlugins = result.getSortedPlugins();
  657. // move plugins from "unresolved" to "resolved"
  658. for (String pluginId : sortedPlugins) {
  659. PluginWrapper pluginWrapper = plugins.get(pluginId);
  660. if (unresolvedPlugins.remove(pluginWrapper)) {
  661. PluginState pluginState = pluginWrapper.getPluginState();
  662. if (pluginState != PluginState.DISABLED) {
  663. pluginWrapper.setPluginState(PluginState.RESOLVED);
  664. }
  665. resolvedPlugins.add(pluginWrapper);
  666. log.info("Plugin '{}' resolved", getPluginLabel(pluginWrapper.getDescriptor()));
  667. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  668. }
  669. }
  670. }
  671. protected synchronized void firePluginStateEvent(PluginStateEvent event) {
  672. for (PluginStateListener listener : pluginStateListeners) {
  673. log.trace("Fire '{}' to '{}'", event, listener);
  674. listener.pluginStateChanged(event);
  675. }
  676. }
  677. protected PluginWrapper loadPluginFromPath(Path pluginPath) {
  678. // Test for plugin path duplication
  679. String pluginId = idForPath(pluginPath);
  680. if (pluginId != null) {
  681. throw new PluginAlreadyLoadedException(pluginId, pluginPath);
  682. }
  683. // Retrieve and validate the plugin descriptor
  684. PluginDescriptorFinder pluginDescriptorFinder = getPluginDescriptorFinder();
  685. log.debug("Use '{}' to find plugins descriptors", pluginDescriptorFinder);
  686. log.debug("Finding plugin descriptor for plugin '{}'", pluginPath);
  687. PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginPath);
  688. validatePluginDescriptor(pluginDescriptor);
  689. // Check there are no loaded plugins with the retrieved id
  690. pluginId = pluginDescriptor.getPluginId();
  691. if (plugins.containsKey(pluginId)) {
  692. PluginWrapper loadedPlugin = getPlugin(pluginId);
  693. throw new PluginRuntimeException("There is an already loaded plugin ({}) "
  694. + "with the same id ({}) as the plugin at path '{}'. Simultaneous loading "
  695. + "of plugins with the same PluginId is not currently supported.\n"
  696. + "As a workaround you may include PluginVersion and PluginProvider "
  697. + "in PluginId.",
  698. loadedPlugin, pluginId, pluginPath);
  699. }
  700. log.debug("Found descriptor {}", pluginDescriptor);
  701. String pluginClassName = pluginDescriptor.getPluginClass();
  702. log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
  703. // load plugin
  704. log.debug("Loading plugin '{}'", pluginPath);
  705. ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
  706. log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
  707. PluginWrapper pluginWrapper = createPluginWrapper(pluginDescriptor, pluginPath, pluginClassLoader);
  708. // test for disabled plugin
  709. if (isPluginDisabled(pluginDescriptor.getPluginId())) {
  710. log.info("Plugin '{}' is disabled", pluginPath);
  711. pluginWrapper.setPluginState(PluginState.DISABLED);
  712. }
  713. // validate the plugin
  714. if (!isPluginValid(pluginWrapper)) {
  715. log.warn("Plugin '{}' is invalid and it will be disabled", pluginPath);
  716. pluginWrapper.setPluginState(PluginState.DISABLED);
  717. }
  718. log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
  719. pluginId = pluginDescriptor.getPluginId();
  720. // add plugin to the list with plugins
  721. plugins.put(pluginId, pluginWrapper);
  722. getUnresolvedPlugins().add(pluginWrapper);
  723. // add plugin class loader to the list with class loaders
  724. getPluginClassLoaders().put(pluginId, pluginClassLoader);
  725. return pluginWrapper;
  726. }
  727. /**
  728. * creates the plugin wrapper. override this if you want to prevent plugins having full access to the plugin manager
  729. *
  730. * @return
  731. */
  732. protected PluginWrapper createPluginWrapper(PluginDescriptor pluginDescriptor, Path pluginPath, ClassLoader pluginClassLoader) {
  733. // create the plugin wrapper
  734. log.debug("Creating wrapper for plugin '{}'", pluginPath);
  735. PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
  736. pluginWrapper.setPluginFactory(getPluginFactory());
  737. return pluginWrapper;
  738. }
  739. /**
  740. * Tests for already loaded plugins on given path.
  741. *
  742. * @param pluginPath the path to investigate
  743. * @return id of plugin or null if not loaded
  744. */
  745. protected String idForPath(Path pluginPath) {
  746. for (PluginWrapper plugin : plugins.values()) {
  747. if (plugin.getPluginPath().equals(pluginPath)) {
  748. return plugin.getPluginId();
  749. }
  750. }
  751. return null;
  752. }
  753. /**
  754. * Override this to change the validation criteria.
  755. *
  756. * @param descriptor the plugin descriptor to validate
  757. * @throws PluginRuntimeException if validation fails
  758. */
  759. protected void validatePluginDescriptor(PluginDescriptor descriptor) {
  760. if (StringUtils.isNullOrEmpty(descriptor.getPluginId())) {
  761. throw new PluginRuntimeException("Field 'id' cannot be empty");
  762. }
  763. if (descriptor.getVersion() == null) {
  764. throw new PluginRuntimeException("Field 'version' cannot be empty");
  765. }
  766. }
  767. /**
  768. * @return true if exact versions in requires is allowed
  769. */
  770. public boolean isExactVersionAllowed() {
  771. return exactVersionAllowed;
  772. }
  773. /**
  774. * Set to true to allow requires expression to be exactly x.y.z.
  775. * The default is false, meaning that using an exact version x.y.z will
  776. * implicitly mean the same as &gt;=x.y.z
  777. *
  778. * @param exactVersionAllowed set to true or false
  779. */
  780. public void setExactVersionAllowed(boolean exactVersionAllowed) {
  781. this.exactVersionAllowed = exactVersionAllowed;
  782. }
  783. @Override
  784. public VersionManager getVersionManager() {
  785. return versionManager;
  786. }
  787. /**
  788. * The plugin label is used in logging and it's a string in format {@code pluginId@pluginVersion}.
  789. */
  790. protected String getPluginLabel(PluginDescriptor pluginDescriptor) {
  791. return pluginDescriptor.getPluginId() + "@" + pluginDescriptor.getVersion();
  792. }
  793. @SuppressWarnings("unchecked")
  794. protected <T> List<Class<? extends T>> getExtensionClasses(List<ExtensionWrapper<T>> extensionsWrapper) {
  795. List<Class<? extends T>> extensionClasses = new ArrayList<>(extensionsWrapper.size());
  796. for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
  797. Class<T> c = (Class<T>) extensionWrapper.getDescriptor().extensionClass;
  798. extensionClasses.add(c);
  799. }
  800. return extensionClasses;
  801. }
  802. protected <T> List<T> getExtensions(List<ExtensionWrapper<T>> extensionsWrapper) {
  803. List<T> extensions = new ArrayList<>(extensionsWrapper.size());
  804. for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
  805. try {
  806. extensions.add(extensionWrapper.getExtension());
  807. } catch (PluginRuntimeException e) {
  808. log.error("Cannot retrieve extension", e);
  809. }
  810. }
  811. return extensions;
  812. }
  813. }