/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.plugin.internal.manager; import static com.google.common.collect.Lists.newArrayList; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.qcadoo.model.beans.qcadooPlugin.QcadooPluginPlugin; import com.qcadoo.plugin.api.Plugin; import com.qcadoo.plugin.api.PluginDependencyInformation; import com.qcadoo.plugin.api.PluginDependencyResult; import com.qcadoo.plugin.api.PluginManager; import com.qcadoo.plugin.api.PluginOperationResult; import com.qcadoo.plugin.api.PluginState; import com.qcadoo.plugin.api.artifact.PluginArtifact; import com.qcadoo.plugin.internal.PluginException; import com.qcadoo.plugin.internal.api.InternalPlugin; import com.qcadoo.plugin.internal.api.InternalPluginAccessor; import com.qcadoo.plugin.internal.api.PluginDao; import com.qcadoo.plugin.internal.api.PluginDependencyManager; import com.qcadoo.plugin.internal.api.PluginDescriptorParser; import com.qcadoo.plugin.internal.api.PluginFileManager; import com.qcadoo.plugin.internal.api.PluginOperationResultImpl; import com.qcadoo.plugin.internal.dependencymanager.PluginStatusResolver; import com.qcadoo.plugin.internal.dependencymanager.SimplePluginStatusResolver; @Service public class DefaultPluginManager implements PluginManager { private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class); @Autowired private InternalPluginAccessor pluginAccessor; @Autowired private PluginDao pluginDao; @Autowired private PluginFileManager pluginFileManager; @Autowired private PluginDependencyManager pluginDependencyManager; @Autowired private PluginDescriptorParser pluginDescriptorParser; private final PluginStatusResolver pluginStatusResolver = new SimplePluginStatusResolver(); @Override public PluginOperationResult enablePlugin(final String... keys) { List<Plugin> plugins = new ArrayList<Plugin>(); for (String key : keys) { Plugin plugin = pluginAccessor.getPlugin(key); if (!plugin.hasState(PluginState.ENABLED)) { plugins.add(plugin); } } if (plugins.isEmpty()) { return PluginOperationResultImpl.success(); } PluginDependencyResult pluginDependencyResult = pluginDependencyManager.getDependenciesToEnable(plugins, pluginStatusResolver); if (pluginDependencyResult.isCyclic()) { return PluginOperationResultImpl.dependenciesCyclesExists(); } if (!pluginDependencyResult.isDependenciesSatisfied()) { if (!pluginDependencyResult.getUnsatisfiedDependencies().isEmpty()) { return PluginOperationResultImpl.unsatisfiedDependencies(pluginDependencyResult); } if (!pluginDependencyResult.getDependenciesToEnable().isEmpty()) { return PluginOperationResultImpl.dependenciesToEnable(pluginDependencyResult); } } boolean shouldRestart = false; List<String> fileNames = new ArrayList<String>(); for (Plugin plugin : plugins) { if (plugin.hasState(PluginState.TEMPORARY)) { fileNames.add(plugin.getFilename()); } } if (!fileNames.isEmpty()) { if (!pluginFileManager.installPlugin(fileNames.toArray(new String[fileNames.size()]))) { return PluginOperationResultImpl.cannotInstallPlugin(); } shouldRestart = true; } plugins = pluginDependencyManager.sortPluginsInDependencyOrder(plugins); for (Plugin plugin : plugins) { if (plugin.hasState(PluginState.TEMPORARY)) { ((InternalPlugin) plugin).changeStateTo(PluginState.ENABLING); } else { try { ((InternalPlugin) plugin).changeStateTo(PluginState.ENABLED); } catch (Exception e) { LOG.error(e.getMessage()); ((InternalPlugin) plugin).changeStateTo(PluginState.DISABLED); return PluginOperationResultImpl.pluginEnablingEncounteredErrors(); } } pluginDao.save(plugin); pluginAccessor.savePlugin(plugin); } if (shouldRestart) { return PluginOperationResultImpl.successWithRestart(); } else { return PluginOperationResultImpl.success(); } } @Override public PluginOperationResult disablePlugin(final String... keys) { List<Plugin> plugins = new ArrayList<>(); for (String key : keys) { Plugin plugin = pluginAccessor.getPlugin(key); if (plugin.isSystemPlugin()) { return PluginOperationResultImpl.systemPluginDisabling(); } if (plugin.hasState(PluginState.ENABLED)) { plugins.add(plugin); } } if (plugins.isEmpty()) { return PluginOperationResultImpl.success(); } PluginDependencyResult pluginDependencyResult = pluginDependencyManager.getDependenciesToDisable(plugins, pluginStatusResolver); if (!pluginDependencyResult.isDependenciesSatisfied() && !pluginDependencyResult.getDependenciesToDisable().isEmpty()) { return PluginOperationResultImpl.dependenciesToDisable(pluginDependencyResult); } plugins = pluginDependencyManager.sortPluginsInDependencyOrder(plugins); Collections.reverse(plugins); for (Plugin plugin : plugins) { ((InternalPlugin) plugin).changeStateTo(PluginState.DISABLED); pluginDao.save(plugin); pluginAccessor.savePlugin(plugin); } return PluginOperationResultImpl.success(); } @Override public PluginOperationResult uninstallPlugin(final String... keys) { List<Plugin> plugins = new ArrayList<Plugin>(); for (String key : keys) { Plugin plugin = pluginAccessor.getPlugin(key); if (plugin.isSystemPlugin()) { return PluginOperationResultImpl.systemPluginUninstalling(); } plugins.add(plugin); } PluginDependencyResult pluginDependencyResult = pluginDependencyManager.getDependenciesToUninstall(plugins, pluginStatusResolver); if (!pluginDependencyResult.isDependenciesSatisfied() && !pluginDependencyResult.getDependenciesToUninstall().isEmpty()) { return PluginOperationResultImpl.dependenciesToUninstall(pluginDependencyResult); } boolean shouldRestart = false; List<String> fileNames = new ArrayList<String>(); for (Plugin plugin : plugins) { if (!plugin.hasState(PluginState.TEMPORARY)) { shouldRestart = true; } fileNames.add(plugin.getFilename()); } pluginFileManager.uninstallPlugin(fileNames.toArray(new String[fileNames.size()])); plugins = pluginDependencyManager.sortPluginsInDependencyOrder(plugins); Collections.reverse(plugins); for (Plugin plugin : plugins) { if (plugin.hasState(PluginState.ENABLED)) { ((InternalPlugin) plugin).changeStateTo(PluginState.DISABLED); } pluginDao.delete(plugin); pluginAccessor.removePlugin(plugin); } if (shouldRestart) { return PluginOperationResultImpl.successWithRestart(); } else { return PluginOperationResultImpl.success(); } } @Override public PluginOperationResult installPlugin(final PluginArtifact pluginArtifact) { File pluginFile = null; try { pluginFile = pluginFileManager.uploadPlugin(pluginArtifact); } catch (PluginException e) { return PluginOperationResultImpl.cannotUploadPlugin(); } Plugin plugin = null; try { plugin = pluginDescriptorParser.parse(pluginFile); } catch (PluginException e) { LOG.error(e.getMessage()); pluginFileManager.uninstallPlugin(pluginFile.getName()); return PluginOperationResultImpl.corruptedPlugin(); } if (plugin.isSystemPlugin()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.systemPluginUpdating(); } boolean shouldRestart = false; PluginDependencyResult pluginDependencyResult = pluginDependencyManager.getDependenciesToEnable(newArrayList(plugin), pluginStatusResolver); if (pluginDependencyResult.isCyclic()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.dependenciesCyclesExists(); } Plugin existingPlugin = pluginAccessor.getPlugin(plugin.getIdentifier()); if (existingPlugin == null) { ((InternalPlugin) plugin).changeStateTo(PluginState.TEMPORARY); pluginDao.save(plugin); pluginAccessor.savePlugin(plugin); if (!pluginDependencyResult.isDependenciesSatisfied() && !pluginDependencyResult.getUnsatisfiedDependencies().isEmpty()) { return PluginOperationResultImpl.successWithMissingDependencies(pluginDependencyResult); } return PluginOperationResultImpl.success(); } else { if (existingPlugin.getVersion().compareTo(plugin.getVersion()) >= 0) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.cannotDowngradePlugin(); } if (existingPlugin.hasState(PluginState.TEMPORARY)) { if (!pluginDependencyResult.isDependenciesSatisfied() && !pluginDependencyResult.getUnsatisfiedDependencies().isEmpty()) { pluginFileManager.uninstallPlugin(existingPlugin.getFilename()); ((InternalPlugin) plugin).changeStateTo(existingPlugin.getState()); pluginDao.save(plugin); pluginAccessor.savePlugin(plugin); return PluginOperationResultImpl.successWithMissingDependencies(pluginDependencyResult); } ((InternalPlugin) plugin).changeStateTo(existingPlugin.getState()); } else if (existingPlugin.hasState(PluginState.DISABLED)) { if (!pluginDependencyResult.isDependenciesSatisfied() && !pluginDependencyResult.getUnsatisfiedDependencies().isEmpty()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.unsatisfiedDependencies(pluginDependencyResult); } if (!pluginFileManager.installPlugin(plugin.getFilename())) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.cannotInstallPlugin(); } shouldRestart = true; ((InternalPlugin) plugin).changeStateTo(existingPlugin.getState()); } else if (existingPlugin.hasState(PluginState.ENABLED)) { if (!pluginDependencyResult.isDependenciesSatisfied()) { if (!pluginDependencyResult.getUnsatisfiedDependencies().isEmpty()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.unsatisfiedDependencies(pluginDependencyResult); } if (!pluginDependencyResult.getDependenciesToEnable().isEmpty()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.dependenciesToEnable(pluginDependencyResult); } } if (!pluginFileManager.installPlugin(plugin.getFilename())) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.cannotInstallPlugin(); } shouldRestart = true; PluginDependencyResult installPluginDependencyResult = pluginDependencyManager.getDependenciesToUpdate( existingPlugin, plugin, pluginStatusResolver); if (!installPluginDependencyResult.getDependenciesToDisableUnsatisfiedAfterUpdate().isEmpty()) { pluginFileManager.uninstallPlugin(plugin.getFilename()); return PluginOperationResultImpl.unsatisfiedDependenciesAfterUpdate(installPluginDependencyResult); } List<Plugin> dependencyPlugins = new ArrayList<Plugin>(); for (PluginDependencyInformation pluginDependencyInformation : installPluginDependencyResult .getDependenciesToDisable()) { dependencyPlugins.add(pluginAccessor.getPlugin(pluginDependencyInformation.getIdentifier())); } dependencyPlugins = pluginDependencyManager.sortPluginsInDependencyOrder(dependencyPlugins); Collections.reverse(dependencyPlugins); for (Plugin dependencyPlugin : dependencyPlugins) { ((InternalPlugin) dependencyPlugin).changeStateTo(PluginState.DISABLED); } ((InternalPlugin) existingPlugin).changeStateTo(PluginState.DISABLED); ((InternalPlugin) plugin).changeStateTo(PluginState.ENABLING); Collections.reverse(dependencyPlugins); for (Plugin dependencyPlugin : dependencyPlugins) { ((InternalPlugin) dependencyPlugin).changeStateTo(PluginState.ENABLING); pluginDao.save(dependencyPlugin); } } pluginFileManager.uninstallPlugin(existingPlugin.getFilename()); pluginDao.save(plugin); pluginAccessor.savePlugin(plugin); if (shouldRestart) { return PluginOperationResultImpl.successWithRestart(); } else { return PluginOperationResultImpl.success(); } } } void setPluginAccessor(final InternalPluginAccessor pluginAccessor) { this.pluginAccessor = pluginAccessor; } void setPluginDao(final PluginDao pluginDao) { this.pluginDao = pluginDao; } void setPluginFileManager(final PluginFileManager pluginFileManager) { this.pluginFileManager = pluginFileManager; } void setPluginDependencyManager(final PluginDependencyManager pluginDependencyManager) { this.pluginDependencyManager = pluginDependencyManager; } void setPluginDescriptorParser(final PluginDescriptorParser pluginDescriptorParser) { this.pluginDescriptorParser = pluginDescriptorParser; } @Override public List<Plugin> getEnabledPluginsList() { List<Plugin> pluginIdentifierList = new ArrayList<Plugin>(); Set<QcadooPluginPlugin> pluginList = pluginDao.list(); for (QcadooPluginPlugin qcadooPlugin : pluginList) { Plugin plugin = pluginAccessor.getEnabledPlugin(qcadooPlugin.getIdentifier()); if (plugin != null) { pluginIdentifierList.add(plugin); } } return pluginIdentifierList; } @Override public boolean isPluginEnabled(final String pluginIdentifier) { return pluginAccessor.getEnabledPlugin(pluginIdentifier) != null; } }