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

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