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.

PluginInfo.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.plugins;
  18. import com.dmdirc.config.ConfigFileBackedConfigProvider;
  19. import com.dmdirc.config.InvalidIdentityFileException;
  20. import com.dmdirc.events.PluginLoadedEvent;
  21. import com.dmdirc.events.PluginUnloadedEvent;
  22. import com.dmdirc.events.eventbus.EventBus;
  23. import com.dmdirc.config.provider.ConfigProvider;
  24. import com.dmdirc.interfaces.config.IdentityController;
  25. import com.dmdirc.util.validators.ValidationResponse;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.nio.file.DirectoryStream;
  29. import java.nio.file.FileSystem;
  30. import java.nio.file.FileSystems;
  31. import java.nio.file.FileVisitResult;
  32. import java.nio.file.Files;
  33. import java.nio.file.Path;
  34. import java.nio.file.SimpleFileVisitor;
  35. import java.nio.file.attribute.BasicFileAttributes;
  36. import java.util.ArrayList;
  37. import java.util.Collection;
  38. import java.util.Collections;
  39. import java.util.HashMap;
  40. import java.util.List;
  41. import java.util.Map;
  42. import org.slf4j.Logger;
  43. import org.slf4j.LoggerFactory;
  44. import dagger.ObjectGraph;
  45. import static com.dmdirc.util.LogUtils.APP_ERROR;
  46. import static com.dmdirc.util.LogUtils.USER_ERROR;
  47. /**
  48. * Stores plugin metadata and handles loading of plugin resources.
  49. */
  50. public class PluginInfo implements ServiceProvider {
  51. private static final Logger LOG = LoggerFactory.getLogger(PluginInfo.class);
  52. /** The metadata for this plugin. */
  53. private final PluginMetaData metaData;
  54. /** The manager to use to look up other plugins. */
  55. private final PluginManager pluginManager;
  56. /** The manager to register services with. */
  57. private final ServiceManager serviceManager;
  58. /** The object graph to pass to plugins for DI purposes. */
  59. private final ObjectGraph objectGraph;
  60. /** The controller to add and remove settings from. */
  61. private final IdentityController identityController;
  62. /** Filename for this plugin (taken from URL). */
  63. private final String filename;
  64. /** The actual Plugin from this jar. */
  65. private Plugin plugin;
  66. /** The classloader used for this Plugin. */
  67. private PluginClassLoader pluginClassLoader;
  68. /** List of classes this plugin has. */
  69. private final List<String> myClasses = new ArrayList<>();
  70. /** Last Error Message. */
  71. private String lastError = "No Error";
  72. /** Are we trying to load? */
  73. private boolean isLoading;
  74. /** List of services we provide. */
  75. private final List<Service> provides = new ArrayList<>();
  76. /** List of children of this plugin. */
  77. private final List<PluginInfo> children = new ArrayList<>();
  78. /** Map of exports. */
  79. private final Map<String, ExportInfo> exports = new HashMap<>();
  80. /** List of configuration providers. */
  81. private final Collection<ConfigFileBackedConfigProvider> configProviders = new ArrayList<>();
  82. /** Event bus to post plugin loaded events to. */
  83. private final EventBus eventBus;
  84. /** File system for the plugin's jar. */
  85. private final FileSystem pluginFilesystem;
  86. /**
  87. * Create a new PluginInfo.
  88. *
  89. * @param metadata The plugin's metadata information
  90. * @param eventBus Event bus to post event loaded events on.
  91. * @param identityController The identity controller to add and remove settings from.
  92. * @param objectGraph The object graph to give to plugins for DI purposes.
  93. *
  94. * @throws PluginException if there is an error loading the Plugin
  95. */
  96. public PluginInfo(
  97. final PluginManager pluginManager,
  98. final ServiceManager serviceManager,
  99. final PluginMetaData metadata,
  100. final EventBus eventBus,
  101. final IdentityController identityController,
  102. final ObjectGraph objectGraph) throws PluginException {
  103. this.pluginManager = pluginManager;
  104. this.serviceManager = serviceManager;
  105. this.objectGraph = objectGraph;
  106. this.eventBus = eventBus;
  107. this.identityController = identityController;
  108. this.filename = metadata.getPluginPath().getFileName().toString();
  109. this.metaData = metadata;
  110. try {
  111. pluginFilesystem = FileSystems.newFileSystem(metadata.getPluginPath(), null);
  112. } catch (IOException ex) {
  113. lastError = "Error loading filesystem: " + ex.getMessage();
  114. throw new PluginException("Plugin " + filename + " failed to load. " + lastError, ex);
  115. }
  116. updateClassList();
  117. if (!myClasses.contains(metadata.getMainClass())) {
  118. lastError = "main class file (" + metadata.getMainClass() + ") not found in jar.";
  119. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  120. }
  121. updateProvides();
  122. getDefaults();
  123. }
  124. public PluginMetaData getMetaData() {
  125. return metaData;
  126. }
  127. public String getFilename() {
  128. return filename;
  129. }
  130. public Plugin getPlugin() {
  131. return plugin;
  132. }
  133. public PluginClassLoader getPluginClassLoader() {
  134. return pluginClassLoader;
  135. }
  136. public String getLastError() {
  137. return lastError;
  138. }
  139. /**
  140. * Updates the list of known classes within this plugin from the specified resource manager.
  141. */
  142. private void updateClassList() throws PluginException {
  143. myClasses.clear();
  144. try {
  145. Files.walkFileTree(pluginFilesystem.getPath("/"), new SimpleFileVisitor<Path>() {
  146. @Override
  147. public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
  148. if (file.getFileName().toString().endsWith(".class")) {
  149. final String classname = file.toAbsolutePath().toString().replace('/',
  150. '.');
  151. myClasses.add(classname.substring(1, classname.length() - 6));
  152. }
  153. return FileVisitResult.CONTINUE;
  154. }
  155. });
  156. } catch (IOException ex) {
  157. lastError = "Error loading classes: " + ex.getMessage();
  158. throw new PluginException("Plugin " + filename + " failed to load. " + lastError, ex);
  159. }
  160. }
  161. /**
  162. * Gets the {@link ObjectGraph} that should be used when loading this plugin.
  163. *
  164. * <p>
  165. * Where this plugin has a parent which returns a non-null graph from
  166. * {@link Plugin#getObjectGraph()} that object graph will be used unmodified. Otherwise, the
  167. * global object graph will be used.
  168. *
  169. * @return An {@link ObjectGraph} to be used.
  170. */
  171. protected ObjectGraph getObjectGraph() {
  172. if (metaData.getParent() != null) {
  173. final PluginInfo parentInfo = pluginManager.getPluginInfoByName(metaData.getParent());
  174. final Plugin parent = parentInfo.getPlugin();
  175. final ObjectGraph parentGraph = parent.getObjectGraph();
  176. if (parentGraph != null) {
  177. return parentGraph;
  178. }
  179. }
  180. return objectGraph;
  181. }
  182. /**
  183. * Get the defaults, formatters and icons for this plugin.
  184. */
  185. private void getDefaults() {
  186. final ConfigProvider defaults = identityController.getAddonSettings();
  187. LOG.trace("{}: Using domain '{}'", new Object[]{metaData.getName(), getDomain()});
  188. for (Map.Entry<String, String> entry : metaData.getDefaultSettings().entrySet()) {
  189. final String key = entry.getKey();
  190. final String value = entry.getValue();
  191. defaults.setOption(getDomain(), key, value);
  192. }
  193. for (Map.Entry<String, String> entry : metaData.getFormatters().entrySet()) {
  194. final String key = entry.getKey();
  195. final String value = entry.getValue();
  196. defaults.setOption("formatter", key, value);
  197. }
  198. for (Map.Entry<String, String> entry : metaData.getIcons().entrySet()) {
  199. final String key = entry.getKey();
  200. final String value = entry.getValue();
  201. defaults.setOption("icon", key, value);
  202. }
  203. }
  204. /**
  205. * Try get the identities for this plugin. This will unload any identities previously loaded by
  206. * this plugin.
  207. */
  208. private void loadIdentities() {
  209. if (!Files.exists(pluginFilesystem.getPath("/META-INF/identities/"))) {
  210. return;
  211. }
  212. try {
  213. unloadIdentities();
  214. try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(
  215. pluginFilesystem.getPath("/META-INF/identities/"))) {
  216. directoryStream.forEach(this::loadIdentity);
  217. }
  218. } catch (final IOException ioe) {
  219. LOG.warn(USER_ERROR, "Error finding identities in plugin '{}'", metaData.getName(),
  220. ioe);
  221. }
  222. }
  223. private void loadIdentity(final Path path) {
  224. if (!Files.isRegularFile(path)) {
  225. // Don't try to load folders etc
  226. return;
  227. }
  228. try (final InputStream stream = Files.newInputStream(path)) {
  229. synchronized (configProviders) {
  230. final ConfigFileBackedConfigProvider configProvider =
  231. new ConfigFileBackedConfigProvider(stream, false);
  232. identityController.addConfigProvider(configProvider);
  233. configProviders.add(configProvider);
  234. }
  235. } catch (InvalidIdentityFileException ex) {
  236. LOG.warn(USER_ERROR, "Error with identity file '{}' in plugin '{}'",
  237. path.getFileName(), metaData.getName(), ex);
  238. } catch (IOException ex) {
  239. LOG.warn(USER_ERROR, "Unable to load identity file '{}' in plugin '{}'",
  240. path.getFileName(), metaData.getName(), ex);
  241. }
  242. }
  243. /**
  244. * Unload any identities loaded by this plugin.
  245. */
  246. private void unloadIdentities() {
  247. synchronized (configProviders) {
  248. configProviders.forEach(identityController::removeConfigProvider);
  249. configProviders.clear();
  250. }
  251. }
  252. /**
  253. * Update provides list.
  254. */
  255. private void updateProvides() {
  256. // Remove us from any existing provides lists.
  257. synchronized (provides) {
  258. for (Service service : provides) {
  259. service.delProvider(this);
  260. }
  261. provides.clear();
  262. }
  263. // Get services provided by this plugin
  264. final Collection<String> providesList = metaData.getServices();
  265. if (providesList != null) {
  266. for (String item : providesList) {
  267. final String[] bits = item.split(" ");
  268. final String name = bits[0];
  269. final String type = bits.length > 1 ? bits[1] : "misc";
  270. if (!"any".equalsIgnoreCase(name) && !"export".equalsIgnoreCase(type)) {
  271. final Service service = serviceManager.getService(type, name, true);
  272. synchronized (provides) {
  273. service.addProvider(this);
  274. provides.add(service);
  275. }
  276. }
  277. }
  278. }
  279. updateExports();
  280. }
  281. /**
  282. * Called when the plugin is updated using the updater. Reloads metaData and updates the list of
  283. * files.
  284. */
  285. public void pluginUpdated() throws PluginException {
  286. updateClassList();
  287. updateMetaData();
  288. updateProvides();
  289. getDefaults();
  290. }
  291. /**
  292. * Try to reload the metaData from the plugin.config file. If this fails, the old data will be
  293. * used still.
  294. *
  295. * @return true if metaData was reloaded ok, else false.
  296. */
  297. private boolean updateMetaData() {
  298. metaData.load();
  299. return !metaData.hasErrors();
  300. }
  301. /**
  302. * Returns a path inside the plugin's jar for the given name.
  303. *
  304. * @param first The path string or initial part of the path string
  305. * @param more Additional strings to be joined to form the full path
  306. *
  307. * @return The resulting path inside the plugin's jar
  308. */
  309. public Path getPath(final String first, final String... more) {
  310. return pluginFilesystem.getPath(first, more);
  311. }
  312. @Override
  313. public final boolean isActive() {
  314. return isLoaded();
  315. }
  316. @Override
  317. public void activateServices() {
  318. loadPlugin();
  319. }
  320. @Override
  321. public String getProviderName() {
  322. return "Plugin: " + metaData.getFriendlyName() + " ("
  323. + metaData.getName() + " / " + getFilename() + ')';
  324. }
  325. @Override
  326. public List<Service> getServices() {
  327. synchronized (provides) {
  328. return new ArrayList<>(provides);
  329. }
  330. }
  331. /**
  332. * Is this plugin loaded?
  333. *
  334. * @return True if the plugin is currently loaded, false otherwise
  335. */
  336. public boolean isLoaded() {
  337. return plugin != null;
  338. }
  339. /**
  340. * Load any required plugins or services.
  341. *
  342. * @return True if all requirements have been satisfied, false otherwise
  343. */
  344. protected boolean loadRequirements() {
  345. return loadRequiredPlugins() && loadRequiredServices();
  346. }
  347. /**
  348. * Attempts to load all services that are required by this plugin.
  349. *
  350. * @return True iff all required services were found and satisfied
  351. */
  352. protected boolean loadRequiredServices() {
  353. for (String serviceInfo : metaData.getRequiredServices()) {
  354. final String[] parts = serviceInfo.split(" ", 2);
  355. if ("any".equals(parts[0])) {
  356. Service best = null;
  357. boolean found = false;
  358. for (Service service : serviceManager.getServicesByType(parts[1])) {
  359. if (service.isActive()) {
  360. found = true;
  361. break;
  362. }
  363. best = service;
  364. }
  365. if (!found && best != null) {
  366. found = best.activate();
  367. }
  368. if (!found) {
  369. return false;
  370. }
  371. } else {
  372. final Service service = serviceManager.getService(parts[1], parts[0]);
  373. if (service == null || !service.activate()) {
  374. return false;
  375. }
  376. }
  377. }
  378. return true;
  379. }
  380. /**
  381. * Attempts to load all plugins that are required by this plugin.
  382. *
  383. * @return True if all required plugins were found and loaded
  384. */
  385. protected boolean loadRequiredPlugins() {
  386. final String required = metaData.getRequirements().get("plugins");
  387. if (required != null) {
  388. for (String pluginName : required.split(",")) {
  389. final String[] data = pluginName.split(":");
  390. if (!data[0].trim().isEmpty() && !loadRequiredPlugin(data[0])) {
  391. return false;
  392. }
  393. }
  394. }
  395. return metaData.getParent() == null || loadRequiredPlugin(metaData.getParent());
  396. }
  397. /**
  398. * Attempts to load the specified required plugin.
  399. *
  400. * @param name The name of the plugin to be loaded
  401. *
  402. * @return True if the plugin was found and loaded, false otherwise
  403. */
  404. protected boolean loadRequiredPlugin(final String name) {
  405. LOG.info("Loading required plugin '{}' for plugin {}",
  406. new Object[]{name, metaData.getName()});
  407. final PluginInfo pi = pluginManager.getPluginInfoByName(name);
  408. if (pi == null) {
  409. return false;
  410. }
  411. pi.loadPlugin();
  412. return true;
  413. }
  414. /**
  415. * Load the plugin files.
  416. */
  417. public void loadPlugin() {
  418. if (isLoaded() || isLoading) {
  419. lastError = "Not Loading: (" + isLoaded() + "||" + isLoading + ')';
  420. return;
  421. }
  422. updateProvides();
  423. isLoading = true;
  424. if (!loadRequirements()) {
  425. isLoading = false;
  426. lastError = "Unable to satisfy dependencies for " + metaData.getName();
  427. return;
  428. }
  429. loadIdentities();
  430. loadMainClass();
  431. if (isLoaded()) {
  432. //TODO plugin loading shouldn't be done from here, event bus shouldn't be here.
  433. eventBus.publishAsync(new PluginLoadedEvent(this));
  434. }
  435. isLoading = false;
  436. }
  437. /**
  438. * Add the given Plugin as a child of this plugin.
  439. *
  440. * @param child Child to add
  441. */
  442. public void addChild(final PluginInfo child) {
  443. children.add(child);
  444. }
  445. /**
  446. * Remove the given Plugin as a child of this plugin.
  447. *
  448. * @param child Child to remove
  449. */
  450. public void delChild(final PluginInfo child) {
  451. children.remove(child);
  452. }
  453. /**
  454. * Returns a list of this plugin's children.
  455. *
  456. * @return List of child plugins
  457. */
  458. public List<PluginInfo> getChildren() {
  459. return Collections.unmodifiableList(children);
  460. }
  461. /**
  462. * Initialises this plugin's classloader.
  463. */
  464. private void initialiseClassLoader() {
  465. if (pluginClassLoader == null) {
  466. final PluginClassLoader[] loaders = new PluginClassLoader[metaData.getParent() == null
  467. ? 0 : 1];
  468. if (metaData.getParent() != null) {
  469. final String parentName = metaData.getParent();
  470. final PluginInfo parent = pluginManager.getPluginInfoByName(parentName);
  471. parent.addChild(this);
  472. loaders[0] = parent.getPluginClassLoader();
  473. if (loaders[0] == null) {
  474. // Not loaded? Try again...
  475. parent.loadPlugin();
  476. loaders[0] = parent.getPluginClassLoader();
  477. if (loaders[0] == null) {
  478. lastError = "Unable to get classloader from required parent '" + parentName
  479. + "' for " + metaData.getName();
  480. return;
  481. }
  482. }
  483. }
  484. pluginClassLoader = new PluginClassLoader(this, pluginManager.getGlobalClassLoader(),
  485. loaders);
  486. }
  487. }
  488. /**
  489. * Load the main class
  490. */
  491. private void loadMainClass() {
  492. final String classname = metaData.getMainClass();
  493. try {
  494. initialiseClassLoader();
  495. // Don't reload a class if its already loaded.
  496. if (pluginClassLoader.isClassLoaded(classname, true)) {
  497. lastError = "Classloader says we are already loaded.";
  498. return;
  499. }
  500. final Class<?> clazz = pluginClassLoader.loadClass(classname);
  501. if (clazz == null) {
  502. lastError = "Class '" + classname + "' was not able to load.";
  503. return;
  504. }
  505. createMainClass(clazz);
  506. } catch (ClassNotFoundException cnfe) {
  507. lastError = "Class not found ('" + filename + ':' + classname + ':'
  508. + classname.equals(metaData.getMainClass()) + "') - "
  509. + cnfe.getMessage();
  510. LOG.info(USER_ERROR, lastError, cnfe);
  511. } catch (NoClassDefFoundError ncdf) {
  512. lastError = "Unable to instantiate plugin ('" + filename + ':'
  513. + classname + ':'
  514. + classname.equals(metaData.getMainClass())
  515. + "') - Unable to find class: " + ncdf.getMessage();
  516. LOG.info(USER_ERROR, lastError, ncdf);
  517. } catch (VerifyError ve) {
  518. lastError = "Unable to instantiate plugin ('" + filename + ':'
  519. + classname + ':'
  520. + classname.equals(metaData.getMainClass())
  521. + "') - Incompatible: " + ve.getMessage();
  522. LOG.info(USER_ERROR, lastError, ve);
  523. } catch (IllegalArgumentException | ReflectiveOperationException ex) {
  524. lastError = "Unable to instantiate class for plugin " + metaData.getName()
  525. + ": " + classname;
  526. LOG.info(APP_ERROR, lastError, ex);
  527. }
  528. }
  529. private void createMainClass(final Class<?> clazz) throws InstantiationException,
  530. IllegalAccessException {
  531. final Object temp = clazz.newInstance();
  532. if (temp instanceof Plugin) {
  533. final ValidationResponse prerequisites = ((Plugin) temp).checkPrerequisites();
  534. if (prerequisites.isFailure()) {
  535. lastError = "Prerequisites for plugin not met. ('"
  536. + filename + ':' + metaData.getMainClass()
  537. + "' -> '" + prerequisites.getFailureReason() + "') ";
  538. LOG.info(USER_ERROR, lastError);
  539. } else {
  540. initialisePlugin((Plugin) temp);
  541. }
  542. }
  543. }
  544. private void initialisePlugin(final Plugin temp) {
  545. plugin = temp;
  546. try {
  547. plugin.load(this, getObjectGraph());
  548. plugin.onLoad();
  549. } catch (LinkageError | Exception e) {
  550. lastError = "Error in onLoad for " + metaData.getName() + ':' + e.getMessage();
  551. LOG.warn(APP_ERROR, lastError, e);
  552. unloadPlugin();
  553. }
  554. }
  555. /**
  556. * Gets the configuration domain that should be used by this plugin.
  557. *
  558. * @return The configuration domain to use.
  559. */
  560. public String getDomain() {
  561. return "plugin-" + metaData.getName();
  562. }
  563. /**
  564. * Unload the plugin if possible.
  565. */
  566. public void unloadPlugin() {
  567. unloadPlugin(false);
  568. }
  569. /**
  570. * Can this plugin be unloaded? Will return false if: - The plugin is persistent (all its
  571. * classes are loaded into the global class loader) - The plugin isn't currently loaded - The
  572. * metadata key "unloadable" is set to false, no or 0
  573. *
  574. * @return true if plugin can be unloaded
  575. */
  576. public boolean isUnloadable() {
  577. return !isPersistent() && isLoaded() && metaData.isUnloadable();
  578. }
  579. /**
  580. * Unload the plugin if possible.
  581. *
  582. * @param parentUnloading is our parent already unloading? (if so, don't call delChild)
  583. */
  584. private void unloadPlugin(final boolean parentUnloading) {
  585. if (isUnloadable()) {
  586. // Unload all children
  587. for (PluginInfo child : children) {
  588. child.unloadPlugin(true);
  589. }
  590. // Delete ourself as a child of our parent.
  591. if (!parentUnloading && metaData.getParent() != null) {
  592. final PluginInfo pi = pluginManager.getPluginInfoByName(metaData.getParent());
  593. if (pi != null) {
  594. pi.delChild(this);
  595. }
  596. }
  597. // Now unload ourself
  598. try {
  599. plugin.onUnload();
  600. } catch (Exception | LinkageError e) {
  601. lastError = "Error in onUnload for " + metaData.getName()
  602. + ':' + e + " - " + e.getMessage();
  603. LOG.warn(APP_ERROR, lastError, e);
  604. }
  605. //TODO plugin unloading shouldn't be done from here, event bus shouldn't be here.
  606. eventBus.publishAsync(new PluginUnloadedEvent(this));
  607. synchronized (provides) {
  608. for (Service service : provides) {
  609. service.delProvider(this);
  610. }
  611. provides.clear();
  612. }
  613. }
  614. unloadIdentities();
  615. plugin = null;
  616. pluginClassLoader = null;
  617. }
  618. /**
  619. * Get the list of Classes
  620. *
  621. * @return Classes this plugin has
  622. */
  623. public List<String> getClassList() {
  624. return Collections.unmodifiableList(myClasses);
  625. }
  626. /**
  627. * Is this a persistent plugin?
  628. *
  629. * @return true if persistent, else false
  630. */
  631. public boolean isPersistent() {
  632. return metaData.getPersistentClasses().contains("*");
  633. }
  634. /**
  635. * Get a list of all persistent classes in this plugin
  636. *
  637. * @return List of all persistent classes in this plugin
  638. */
  639. public Collection<String> getPersistentClasses() {
  640. if (isPersistent()) {
  641. return getClassList();
  642. } else {
  643. return metaData.getPersistentClasses();
  644. }
  645. }
  646. /**
  647. * Is this a persistent class?
  648. *
  649. * @param classname class to check persistence of
  650. *
  651. * @return true if file (or whole plugin) is persistent, else false
  652. */
  653. public boolean isPersistent(final String classname) {
  654. return isPersistent() || metaData.getPersistentClasses().contains(classname);
  655. }
  656. /**
  657. * String Representation of this plugin
  658. *
  659. * @return String Representation of this plugin
  660. */
  661. @Override
  662. public String toString() {
  663. return metaData.getFriendlyName() + " - " + filename;
  664. }
  665. /**
  666. * Update exports list.
  667. */
  668. private void updateExports() {
  669. exports.clear();
  670. // Get exports provided by this plugin
  671. final Collection<String> exportsList = metaData.getExports();
  672. for (String item : exportsList) {
  673. final String[] bits = item.split(" ");
  674. if (bits.length > 2) {
  675. final String methodName = bits[0];
  676. final String methodClass = bits[2];
  677. final String serviceName = bits.length > 4 ? bits[4] : bits[0];
  678. // Add a provides for this
  679. final Service service = serviceManager.getService("export", serviceName, true);
  680. synchronized (provides) {
  681. service.addProvider(this);
  682. provides.add(service);
  683. }
  684. // Add is as an export
  685. exports.put(serviceName, new ExportInfo(methodName, methodClass, this));
  686. }
  687. }
  688. }
  689. @Override
  690. public ExportedService getExportedService(final String name) {
  691. if (exports.containsKey(name)) {
  692. return exports.get(name).getExportedService();
  693. } else {
  694. return null;
  695. }
  696. }
  697. /**
  698. * Does this plugin export the specified service?
  699. *
  700. * @param name Name of the service to check
  701. *
  702. * @return true iff the plugin exports the service
  703. */
  704. public boolean hasExportedService(final String name) {
  705. return exports.containsKey(name);
  706. }
  707. }