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

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