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.

DefaultPluginManager.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. /*
  2. * Copyright 2012 Decebal Suiu
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
  5. * the License. You may obtain a copy of the License in the LICENSE file, or at:
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. package ro.fortsoft.pf4j;
  14. import java.io.File;
  15. import java.io.FileFilter;
  16. import java.io.IOException;
  17. import java.util.*;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;
  20. import ro.fortsoft.pf4j.util.*;
  21. /**
  22. * Default implementation of the PluginManager interface.
  23. *
  24. * @author Decebal Suiu
  25. */
  26. public class DefaultPluginManager implements PluginManager {
  27. private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
  28. public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins";
  29. public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins";
  30. private File pluginsDirectory;
  31. private ExtensionFinder extensionFinder;
  32. private PluginDescriptorFinder pluginDescriptorFinder;
  33. private PluginClasspath pluginClasspath;
  34. /**
  35. * A map of plugins this manager is responsible for (the key is the 'pluginId').
  36. */
  37. private Map<String, PluginWrapper> plugins;
  38. /**
  39. * A map of plugin class loaders (he key is the 'pluginId').
  40. */
  41. private Map<String, PluginClassLoader> pluginClassLoaders;
  42. /**
  43. * A relation between 'pluginPath' and 'pluginId'
  44. */
  45. private Map<String, String> pathToIdMap;
  46. /**
  47. * A list with unresolved plugins (unresolved dependency).
  48. */
  49. private List<PluginWrapper> unresolvedPlugins;
  50. /**
  51. * A list with resolved plugins (resolved dependency).
  52. */
  53. private List<PluginWrapper> resolvedPlugins;
  54. /**
  55. * A list with started plugins.
  56. */
  57. private List<PluginWrapper> startedPlugins;
  58. /**
  59. * The registered {@link PluginStateListener}s.
  60. */
  61. private List<PluginStateListener> pluginStateListeners;
  62. /**
  63. * Cache value for the runtime mode. No need to re-read it because it wont change at
  64. * runtime.
  65. */
  66. private RuntimeMode runtimeMode;
  67. /**
  68. * The system version used for comparisons to the plugin requires attribute.
  69. */
  70. private Version systemVersion = Version.ZERO;
  71. private PluginFactory pluginFactory;
  72. private ExtensionFactory extensionFactory;
  73. private PluginStatusProvider pluginStatusProvider;
  74. /**
  75. * The plugins repository.
  76. */
  77. private PluginRepository pluginRepository;
  78. /**
  79. * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
  80. */
  81. public DefaultPluginManager() {
  82. this.pluginsDirectory = createPluginsDirectory();
  83. initialize();
  84. }
  85. /**
  86. * Constructs DefaultPluginManager which the given plugins directory.
  87. *
  88. * @param pluginsDirectory the directory to search for plugins
  89. */
  90. public DefaultPluginManager(File pluginsDirectory) {
  91. this.pluginsDirectory = pluginsDirectory;
  92. initialize();
  93. }
  94. @Override
  95. public void setSystemVersion(Version version) {
  96. systemVersion = version;
  97. }
  98. @Override
  99. public Version getSystemVersion() {
  100. return systemVersion;
  101. }
  102. @Override
  103. public List<PluginWrapper> getPlugins() {
  104. return new ArrayList<PluginWrapper>(plugins.values());
  105. }
  106. @Override
  107. public List<PluginWrapper> getPlugins(PluginState pluginState) {
  108. List<PluginWrapper> plugins= new ArrayList<PluginWrapper>();
  109. for (PluginWrapper plugin : getPlugins()) {
  110. if (pluginState.equals(plugin.getPluginState())) {
  111. plugins.add(plugin);
  112. }
  113. }
  114. return plugins;
  115. }
  116. @Override
  117. public List<PluginWrapper> getResolvedPlugins() {
  118. return resolvedPlugins;
  119. }
  120. @Override
  121. public List<PluginWrapper> getUnresolvedPlugins() {
  122. return unresolvedPlugins;
  123. }
  124. @Override
  125. public List<PluginWrapper> getStartedPlugins() {
  126. return startedPlugins;
  127. }
  128. @Override
  129. public PluginWrapper getPlugin(String pluginId) {
  130. return plugins.get(pluginId);
  131. }
  132. @Override
  133. public String loadPlugin(File pluginArchiveFile) {
  134. if ((pluginArchiveFile == null) || !pluginArchiveFile.exists()) {
  135. throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginArchiveFile));
  136. }
  137. log.debug("Loading plugin from '{}'", pluginArchiveFile);
  138. File pluginDirectory = null;
  139. try {
  140. pluginDirectory = expandPluginArchive(pluginArchiveFile);
  141. } catch (IOException e) {
  142. log.error(e.getMessage(), e);
  143. }
  144. if ((pluginDirectory == null) || !pluginDirectory.exists()) {
  145. throw new IllegalArgumentException(String.format("Failed to expand %s", pluginArchiveFile));
  146. }
  147. try {
  148. PluginWrapper pluginWrapper = loadPluginDirectory(pluginDirectory);
  149. // TODO uninstalled plugin dependencies?
  150. unresolvedPlugins.remove(pluginWrapper);
  151. resolvedPlugins.add(pluginWrapper);
  152. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
  153. return pluginWrapper.getDescriptor().getPluginId();
  154. } catch (PluginException e) {
  155. log.error(e.getMessage(), e);
  156. }
  157. return null;
  158. }
  159. /**
  160. * Start all active plugins.
  161. */
  162. @Override
  163. public void startPlugins() {
  164. for (PluginWrapper pluginWrapper : resolvedPlugins) {
  165. PluginState pluginState = pluginWrapper.getPluginState();
  166. if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
  167. try {
  168. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  169. log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  170. pluginWrapper.getPlugin().start();
  171. pluginWrapper.setPluginState(PluginState.STARTED);
  172. startedPlugins.add(pluginWrapper);
  173. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  174. } catch (PluginException e) {
  175. log.error(e.getMessage(), e);
  176. }
  177. }
  178. }
  179. }
  180. /**
  181. * Start the specified plugin and it's dependencies.
  182. */
  183. @Override
  184. public PluginState startPlugin(String pluginId) {
  185. if (!plugins.containsKey(pluginId)) {
  186. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  187. }
  188. PluginWrapper pluginWrapper = getPlugin(pluginId);
  189. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  190. PluginState pluginState = pluginWrapper.getPluginState();
  191. if (PluginState.STARTED == pluginState) {
  192. log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  193. return PluginState.STARTED;
  194. }
  195. if (PluginState.DISABLED == pluginState) {
  196. // automatically enable plugin on manual plugin start
  197. if (!enablePlugin(pluginId)) {
  198. return pluginState;
  199. }
  200. }
  201. for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
  202. startPlugin(dependency.getPluginId());
  203. }
  204. try {
  205. log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  206. pluginWrapper.getPlugin().start();
  207. pluginWrapper.setPluginState(PluginState.STARTED);
  208. startedPlugins.add(pluginWrapper);
  209. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  210. } catch (PluginException e) {
  211. log.error(e.getMessage(), e);
  212. }
  213. return pluginWrapper.getPluginState();
  214. }
  215. /**
  216. * Stop all active plugins.
  217. */
  218. @Override
  219. public void stopPlugins() {
  220. // stop started plugins in reverse order
  221. Collections.reverse(startedPlugins);
  222. Iterator<PluginWrapper> itr = startedPlugins.iterator();
  223. while (itr.hasNext()) {
  224. PluginWrapper pluginWrapper = itr.next();
  225. PluginState pluginState = pluginWrapper.getPluginState();
  226. if (PluginState.STARTED == pluginState) {
  227. try {
  228. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  229. log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  230. pluginWrapper.getPlugin().stop();
  231. pluginWrapper.setPluginState(PluginState.STOPPED);
  232. itr.remove();
  233. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  234. } catch (PluginException e) {
  235. log.error(e.getMessage(), e);
  236. }
  237. }
  238. }
  239. }
  240. /**
  241. * Stop the specified plugin and it's dependencies.
  242. */
  243. @Override
  244. public PluginState stopPlugin(String pluginId) {
  245. if (!plugins.containsKey(pluginId)) {
  246. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  247. }
  248. PluginWrapper pluginWrapper = getPlugin(pluginId);
  249. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  250. PluginState pluginState = pluginWrapper.getPluginState();
  251. if (PluginState.STOPPED == pluginState) {
  252. log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  253. return PluginState.STOPPED;
  254. }
  255. // test for disabled plugin
  256. if (PluginState.DISABLED == pluginState) {
  257. // do nothing
  258. return pluginState;
  259. }
  260. for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
  261. stopPlugin(dependency.getPluginId());
  262. }
  263. try {
  264. log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  265. pluginWrapper.getPlugin().stop();
  266. pluginWrapper.setPluginState(PluginState.STOPPED);
  267. startedPlugins.remove(pluginWrapper);
  268. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  269. } catch (PluginException e) {
  270. log.error(e.getMessage(), e);
  271. }
  272. return pluginWrapper.getPluginState();
  273. }
  274. /**
  275. * Load plugins.
  276. */
  277. @Override
  278. public void loadPlugins() {
  279. log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath());
  280. // check for plugins directory
  281. if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
  282. log.error("No '{}' directory", pluginsDirectory.getAbsolutePath());
  283. return;
  284. }
  285. // expand all plugin archives
  286. List<File> pluginArchives = pluginRepository.getPluginArchives();
  287. for (File archiveFile : pluginArchives) {
  288. try {
  289. expandPluginArchive(archiveFile);
  290. } catch (IOException e) {
  291. log.error(e.getMessage(), e);
  292. }
  293. }
  294. // check for no plugins
  295. List<FileFilter> filterList = new ArrayList<FileFilter>();
  296. filterList.add(new DirectoryFileFilter());
  297. filterList.add(new NotFileFilter(createHiddenPluginFilter()));
  298. FileFilter pluginsFilter = new AndFileFilter(filterList);
  299. File[] directories = pluginsDirectory.listFiles(pluginsFilter);
  300. if (directories == null) {
  301. directories = new File[0];
  302. }
  303. log.debug("Found {} possible plugins: {}", directories.length, directories);
  304. if (directories.length == 0) {
  305. log.info("No plugins");
  306. return;
  307. }
  308. // load any plugin from plugins directory
  309. for (File directory : directories) {
  310. try {
  311. loadPluginDirectory(directory);
  312. } catch (PluginException e) {
  313. log.error(e.getMessage(), e);
  314. }
  315. }
  316. // resolve 'unresolvedPlugins'
  317. try {
  318. resolvePlugins();
  319. } catch (PluginException e) {
  320. log.error(e.getMessage(), e);
  321. }
  322. }
  323. @Override
  324. public boolean unloadPlugin(String pluginId) {
  325. try {
  326. PluginState pluginState = stopPlugin(pluginId);
  327. if (PluginState.STARTED == pluginState) {
  328. return false;
  329. }
  330. PluginWrapper pluginWrapper = getPlugin(pluginId);
  331. PluginDescriptor descriptor = pluginWrapper.getDescriptor();
  332. List<PluginDependency> dependencies = descriptor.getDependencies();
  333. for (PluginDependency dependency : dependencies) {
  334. if (!unloadPlugin(dependency.getPluginId())) {
  335. return false;
  336. }
  337. }
  338. // remove the plugin
  339. plugins.remove(pluginId);
  340. resolvedPlugins.remove(pluginWrapper);
  341. pathToIdMap.remove(pluginWrapper.getPluginPath());
  342. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  343. // remove the classloader
  344. if (pluginClassLoaders.containsKey(pluginId)) {
  345. PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
  346. classLoader.dispose();
  347. }
  348. return true;
  349. } catch (IllegalArgumentException e) {
  350. // ignore not found exceptions because this method is recursive
  351. }
  352. return false;
  353. }
  354. @Override
  355. public boolean disablePlugin(String pluginId) {
  356. if (!plugins.containsKey(pluginId)) {
  357. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  358. }
  359. PluginWrapper pluginWrapper = getPlugin(pluginId);
  360. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  361. PluginState pluginState = pluginWrapper.getPluginState();
  362. if (PluginState.DISABLED == pluginState) {
  363. log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  364. return true;
  365. }
  366. if (PluginState.STOPPED == stopPlugin(pluginId)) {
  367. pluginWrapper.setPluginState(PluginState.DISABLED);
  368. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
  369. if (!pluginStatusProvider.disablePlugin(pluginId)) {
  370. return false;
  371. }
  372. log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  373. return true;
  374. }
  375. return false;
  376. }
  377. @Override
  378. public boolean enablePlugin(String pluginId) {
  379. if (!plugins.containsKey(pluginId)) {
  380. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  381. }
  382. PluginWrapper pluginWrapper = getPlugin(pluginId);
  383. if (!isPluginValid(pluginWrapper)) {
  384. log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
  385. pluginWrapper.getDescriptor().getVersion());
  386. return false;
  387. }
  388. PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  389. PluginState pluginState = pluginWrapper.getPluginState();
  390. if (PluginState.DISABLED != pluginState) {
  391. log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  392. return true;
  393. }
  394. if (!pluginStatusProvider.enablePlugin(pluginId)) {
  395. return false;
  396. }
  397. pluginWrapper.setPluginState(PluginState.CREATED);
  398. firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
  399. log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
  400. return true;
  401. }
  402. @Override
  403. public boolean deletePlugin(String pluginId) {
  404. if (!plugins.containsKey(pluginId)) {
  405. throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
  406. }
  407. PluginWrapper pluginWrapper = getPlugin(pluginId);
  408. PluginState pluginState = stopPlugin(pluginId);
  409. if (PluginState.STARTED == pluginState) {
  410. log.error("Failed to stop plugin {} on delete", pluginId);
  411. return false;
  412. }
  413. if (!unloadPlugin(pluginId)) {
  414. log.error("Failed to unload plugin {} on delete", pluginId);
  415. return false;
  416. }
  417. File pluginFolder = new File(pluginsDirectory, pluginWrapper.getPluginPath());
  418. if (pluginFolder.exists()) {
  419. FileUtils.delete(pluginFolder);
  420. }
  421. pluginRepository.deletePluginArchive(pluginWrapper.getPluginPath());
  422. return true;
  423. }
  424. /**
  425. * Get plugin class loader for this path.
  426. */
  427. @Override
  428. public PluginClassLoader getPluginClassLoader(String pluginId) {
  429. return pluginClassLoaders.get(pluginId);
  430. }
  431. @Override
  432. public <T> List<T> getExtensions(Class<T> type) {
  433. List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
  434. List<T> extensions = new ArrayList<T>(extensionsWrapper.size());
  435. for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
  436. extensions.add(extensionWrapper.getExtension());
  437. }
  438. return extensions;
  439. }
  440. @Override
  441. public Set<String> getExtensionClassNames(String pluginId) {
  442. return extensionFinder.findClassNames(pluginId);
  443. }
  444. @Override
  445. public RuntimeMode getRuntimeMode() {
  446. if (runtimeMode == null) {
  447. // retrieves the runtime mode from system
  448. String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
  449. runtimeMode = RuntimeMode.byName(modeAsString);
  450. }
  451. return runtimeMode;
  452. }
  453. /**
  454. * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'.
  455. */
  456. public PluginWrapper whichPlugin(Class<?> clazz) {
  457. ClassLoader classLoader = clazz.getClassLoader();
  458. for (PluginWrapper plugin : resolvedPlugins) {
  459. if (plugin.getPluginClassLoader() == classLoader) {
  460. return plugin;
  461. }
  462. }
  463. log.warn("Failed to find the plugin for {}", clazz);
  464. return null;
  465. }
  466. @Override
  467. public synchronized void addPluginStateListener(PluginStateListener listener) {
  468. pluginStateListeners.add(listener);
  469. }
  470. @Override
  471. public synchronized void removePluginStateListener(PluginStateListener listener) {
  472. pluginStateListeners.remove(listener);
  473. }
  474. public Version getVersion() {
  475. String version = null;
  476. Package pf4jPackage = getClass().getPackage();
  477. if (pf4jPackage != null) {
  478. version = pf4jPackage.getImplementationVersion();
  479. if (version == null) {
  480. version = pf4jPackage.getSpecificationVersion();
  481. }
  482. }
  483. return (version != null) ? Version.createVersion(version) : Version.ZERO;
  484. }
  485. /**
  486. * Add the possibility to override the PluginDescriptorFinder.
  487. * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
  488. * PropertiesPluginDescriptorFinder is returned else this method returns
  489. * DefaultPluginDescriptorFinder.
  490. */
  491. protected PluginDescriptorFinder createPluginDescriptorFinder() {
  492. if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
  493. return new PropertiesPluginDescriptorFinder();
  494. }
  495. return new DefaultPluginDescriptorFinder(pluginClasspath);
  496. }
  497. /**
  498. * Add the possibility to override the ExtensionFinder.
  499. */
  500. protected ExtensionFinder createExtensionFinder() {
  501. DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this, extensionFactory);
  502. addPluginStateListener(extensionFinder);
  503. return extensionFinder;
  504. }
  505. /**
  506. * Add the possibility to override the PluginClassPath.
  507. * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
  508. * DevelopmentPluginClasspath is returned else this method returns
  509. * PluginClasspath.
  510. */
  511. protected PluginClasspath createPluginClasspath() {
  512. if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
  513. return new DevelopmentPluginClasspath();
  514. }
  515. return new PluginClasspath();
  516. }
  517. protected PluginStatusProvider createPluginStatusProvider() {
  518. return new DefaultPluginStatusProvider(pluginsDirectory);
  519. }
  520. protected PluginRepository createPluginRepository() {
  521. return new DefaultPluginRepository(pluginsDirectory, new ZipFileFilter());
  522. }
  523. protected boolean isPluginDisabled(String pluginId) {
  524. return pluginStatusProvider.isPluginDisabled(pluginId);
  525. }
  526. protected boolean isPluginValid(PluginWrapper pluginWrapper) {
  527. Version requires = pluginWrapper.getDescriptor().getRequires();
  528. Version system = getSystemVersion();
  529. if (system.isZero() || system.atLeast(requires)) {
  530. return true;
  531. }
  532. log.warn("Plugin '{}:{}' requires a minimum system version of {}",
  533. pluginWrapper.getPluginId(),
  534. pluginWrapper.getDescriptor().getVersion(),
  535. requires);
  536. return false;
  537. }
  538. protected FileFilter createHiddenPluginFilter() {
  539. return new HiddenFilter();
  540. }
  541. /**
  542. * Add the possibility to override the plugins directory.
  543. * If a "pf4j.pluginsDir" system property is defined than this method returns
  544. * that directory.
  545. * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
  546. * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
  547. * DEFAULT_PLUGINS_DIRECTORY ("plugins").
  548. * @return
  549. */
  550. protected File createPluginsDirectory() {
  551. String pluginsDir = System.getProperty("pf4j.pluginsDir");
  552. if (pluginsDir == null) {
  553. if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
  554. pluginsDir = DEVELOPMENT_PLUGINS_DIRECTORY;
  555. } else {
  556. pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
  557. }
  558. }
  559. return new File(pluginsDir);
  560. }
  561. /**
  562. * Add the possibility to override the PluginFactory..
  563. */
  564. protected PluginFactory createPluginFactory() {
  565. return new DefaultPluginFactory();
  566. }
  567. /**
  568. * Add the possibility to override the ExtensionFactory.
  569. */
  570. protected ExtensionFactory createExtensionFactory() {
  571. return new DefaultExtensionFactory();
  572. }
  573. private void initialize() {
  574. plugins = new HashMap<String, PluginWrapper>();
  575. pluginClassLoaders = new HashMap<String, PluginClassLoader>();
  576. pathToIdMap = new HashMap<String, String>();
  577. unresolvedPlugins = new ArrayList<PluginWrapper>();
  578. resolvedPlugins = new ArrayList<PluginWrapper>();
  579. startedPlugins = new ArrayList<PluginWrapper>();
  580. pluginStateListeners = new ArrayList<PluginStateListener>();
  581. log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
  582. pluginClasspath = createPluginClasspath();
  583. pluginFactory = createPluginFactory();
  584. extensionFactory = createExtensionFactory();
  585. pluginDescriptorFinder = createPluginDescriptorFinder();
  586. extensionFinder = createExtensionFinder();
  587. pluginStatusProvider = createPluginStatusProvider();
  588. pluginRepository = createPluginRepository();
  589. System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
  590. }
  591. private PluginWrapper loadPluginDirectory(File pluginDirectory) throws PluginException {
  592. // try to load the plugin
  593. String pluginName = pluginDirectory.getName();
  594. String pluginPath = "/".concat(pluginName);
  595. // test for plugin duplication
  596. if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
  597. return null;
  598. }
  599. // retrieves the plugin descriptor
  600. log.debug("Find plugin descriptor '{}'", pluginPath);
  601. PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
  602. log.debug("Descriptor " + pluginDescriptor);
  603. String pluginClassName = pluginDescriptor.getPluginClass();
  604. log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
  605. // load plugin
  606. log.debug("Loading plugin '{}'", pluginPath);
  607. PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
  608. pluginLoader.load();
  609. log.debug("Loaded plugin '{}'", pluginPath);
  610. // create the plugin wrapper
  611. log.debug("Creating wrapper for plugin '{}'", pluginPath);
  612. PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
  613. pluginWrapper.setPluginFactory(pluginFactory);
  614. pluginWrapper.setRuntimeMode(getRuntimeMode());
  615. // test for disabled plugin
  616. if (isPluginDisabled(pluginDescriptor.getPluginId())) {
  617. log.info("Plugin '{}' is disabled", pluginPath);
  618. pluginWrapper.setPluginState(PluginState.DISABLED);
  619. }
  620. // validate the plugin
  621. if (!isPluginValid(pluginWrapper)) {
  622. log.info("Plugin '{}' is disabled", pluginPath);
  623. pluginWrapper.setPluginState(PluginState.DISABLED);
  624. }
  625. log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
  626. String pluginId = pluginDescriptor.getPluginId();
  627. // add plugin to the list with plugins
  628. plugins.put(pluginId, pluginWrapper);
  629. unresolvedPlugins.add(pluginWrapper);
  630. // add plugin class loader to the list with class loaders
  631. PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
  632. pluginClassLoaders.put(pluginId, pluginClassLoader);
  633. return pluginWrapper;
  634. }
  635. private File expandPluginArchive(File pluginArchiveFile) throws IOException {
  636. String fileName = pluginArchiveFile.getName();
  637. long pluginArchiveDate = pluginArchiveFile.lastModified();
  638. String pluginName = fileName.substring(0, fileName.length() - 4);
  639. File pluginDirectory = new File(pluginsDirectory, pluginName);
  640. // check if exists directory or the '.zip' file is "newer" than directory
  641. if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) {
  642. log.debug("Expand plugin archive '{}' in '{}'", pluginArchiveFile, pluginDirectory);
  643. // do not overwrite an old version, remove it
  644. if (pluginDirectory.exists()) {
  645. FileUtils.delete(pluginDirectory);
  646. }
  647. // create directory for plugin
  648. pluginDirectory.mkdirs();
  649. // expand '.zip' file
  650. Unzip unzip = new Unzip();
  651. unzip.setSource(pluginArchiveFile);
  652. unzip.setDestination(pluginDirectory);
  653. unzip.extract();
  654. }
  655. return pluginDirectory;
  656. }
  657. private void resolvePlugins() throws PluginException {
  658. resolveDependencies();
  659. }
  660. private void resolveDependencies() throws PluginException {
  661. DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
  662. resolvedPlugins = dependencyResolver.getSortedPlugins();
  663. for (PluginWrapper pluginWrapper : resolvedPlugins) {
  664. unresolvedPlugins.remove(pluginWrapper);
  665. log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
  666. }
  667. }
  668. private synchronized void firePluginStateEvent(PluginStateEvent event) {
  669. for (PluginStateListener listener : pluginStateListeners) {
  670. log.debug("Fire '{}' to '{}'", event, listener);
  671. listener.pluginStateChanged(event);
  672. }
  673. }
  674. }