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

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