/** * MIT License * * Copyright (c) 2017 zgqq * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package mah.plugin.loader; import mah.app.ApplicationManager; import mah.app.config.Config; import mah.command.Command; import mah.command.CommandManager; import mah.command.event.InitializeEvent; import mah.event.EventHandler; import mah.plugin.Plugin; import mah.plugin.PluginException; import mah.plugin.PluginMetainfo; import mah.plugin.command.PluginCommand; import mah.plugin.config.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.*; /** * Created by zgq on 2017-01-08 19:51 */ public class SimplePluginLoader { private static final Logger LOGGER = LoggerFactory.getLogger(SimplePluginLoader.class); private final XmlConfigReader reader; private final ExecutorService executorService; private final ScheduledExecutorService scheduledExecutor; private final Logger logger = LoggerFactory.getLogger(SimplePluginLoader.class); public SimplePluginLoader(Config config, List<String> pluginMetainfoFiles) throws Exception { this.reader = new XmlConfigReader(ApplicationManager.getInstance().getPluginDir(), config,pluginMetainfoFiles); executorService = Executors.newCachedThreadPool(); scheduledExecutor = Executors.newScheduledThreadPool(2); } private Plugin loadPlugin(PluginMetainfo pluginMetainfo) throws Exception { Class<?> clazz = pluginMetainfo.getPluginLoader().loadClass(pluginMetainfo.getPluginClass()); Object pluginObj = clazz.newInstance(); if (pluginObj instanceof Plugin) { return (Plugin) pluginObj; } else { throw new PluginException("Plugin is supposed to implement the interface of Plugin"); } } private void setPluginDir(PluginMetainfo pluginMetainfo) { pluginMetainfo.setPluginDataDir(ApplicationManager.getInstance().getPluginDataDir() + File.separator + pluginMetainfo.getName()); } public final List<Plugin> loadPlugins(List<String> pluginNames) { List<PluginMetainfo> activePluginMetainfos = reader.getPluginMetainfos(pluginNames); List<Plugin> plugins; plugins = new ArrayList<>(); for (PluginMetainfo pluginMetainfo : activePluginMetainfos) { Class<?> clazz = null; try { setPluginDir(pluginMetainfo); Plugin plugin = loadPlugin(pluginMetainfo); plugin.setPluginMetainfo(pluginMetainfo); plugins.add(plugin); } catch (Throwable e) { throw new PluginException("Unable to loadPlugins plugin " + clazz, e); } } return plugins; } private Map<String, List<CommandConfig>> loadCommands(List<String> pluginNames) { List<? extends PluginConfig> pluginConfigs = reader.parsePluginConfigs(pluginNames); Map<String, List<CommandConfig>> pluginToCommandConfigs = new HashMap<>(); for (PluginConfig pluginConfig : pluginConfigs) { List<CommandConfig> commandConfigs = new ArrayList<>(); List<? extends CommandConfig> xmlCommandConfigs = pluginConfig.getCommandConfigs(); for (CommandConfig commandConfig : xmlCommandConfigs) { commandConfigs.add(commandConfig); } pluginToCommandConfigs.put(pluginConfig.getName(), commandConfigs); } return pluginToCommandConfigs; } private Command findCommand(CommandConfig commandConfig) { String commandName = commandConfig.getCommandName(); Command command = CommandManager.getInstance().findCommand(commandConfig.getPluginName(), commandName); if (command == null) { throw new PluginException("Not found command " + commandName); } return command; } public final void start() { List<String> activePlugins = reader.getActivePluginNames(); List<Plugin> plugins = loadPlugins(activePlugins); Map<String, List<CommandConfig>> pluginToCommands = loadCommands(activePlugins); for (Plugin plugin : plugins) { List<CommandConfig> commandConfigs = pluginToCommands.get(plugin.getName()); Future<?> loadTask = executorService.submit(() -> { try { long start = System.nanoTime(); startPlugin(plugin, commandConfigs); long end = System.nanoTime(); logger.info("Loading plugin {} took {} milliseconds", plugin, TimeUnit.MILLISECONDS.convert((end - start), TimeUnit.NANOSECONDS)); } catch (Throwable e) { logger.error("Plugin failed to start {}",e); } }); scheduledExecutor.schedule(() -> { if (!loadTask.isDone()) { loadTask.cancel(true); logger.error("Plugin {} took too much time to load,already canceled", plugin); } } , 50000, TimeUnit.MILLISECONDS); } if (plugins.isEmpty()) { LOGGER.warn("No Found any plugin"); } try { executorService.shutdown(); executorService.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new PluginException("Plugins took too much time to load,failing to startup app"); } } private void startPlugin(Plugin plugin, List<CommandConfig> commandConfigs) throws Exception { checkPluginDir(plugin); plugin.init(); plugin.prepare(); if (commandConfigs != null) { for (CommandConfig commandConfig : commandConfigs) { Command command = findCommand(commandConfig); startCommand(plugin, command, commandConfig); CommandManager.getInstance().mapCommand(commandConfig.getTriggerKey(), command); } } } private void checkPluginDir(Plugin plugin) { String pluginDataDir = plugin.getPluginMetainfo().getPluginDataDir(); File pluginData = new File(pluginDataDir); if (!pluginData.exists()) { pluginData.mkdirs(); } } private void startCommand(Plugin plugin, Command command, CommandConfig commandConfig) { configureCommand(command, commandConfig); try { if (command instanceof PluginCommand) { PluginCommand pluginCommand = (PluginCommand) command; pluginCommand.setPlugin(plugin); } List<EventHandler<? extends InitializeEvent>> initializeHandlers = command.getInitializeHandlers(); InitializeEvent initializeEvent = new InitializeEvent(); for (EventHandler initializeHandler : initializeHandlers) { initializeHandler.handle(initializeEvent); } } catch (Exception e) { throw new PluginException(e); } } private void configureCommand(Command command, CommandConfig commandConfig) { if (command instanceof XmlConfigurable) { XmlConfigurable xmlConfigurable = (XmlConfigurable) command; XmlCommandConfig xmlCommandConfig = (XmlCommandConfig) commandConfig; try { Node customConfig = xmlCommandConfig.getCustomConfig(); if (customConfig != null) { xmlConfigurable.configure(xmlCommandConfig.getCustomConfig()); } } catch (Exception e) { throw new PluginException(e); } } } }