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 37KB

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