package com.atlassian.labs.speakeasy.manager; import com.atlassian.labs.speakeasy.external.PluginType; import com.atlassian.labs.speakeasy.commonjs.descriptor.CommonJsModulesDescriptor; import com.atlassian.labs.speakeasy.data.SpeakeasyData; import com.atlassian.labs.speakeasy.manager.convention.ZipPluginTypeHandler; import com.atlassian.labs.speakeasy.product.ProductAccessor; import com.atlassian.plugin.*; import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor; import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor; import com.atlassian.plugin.util.WaitUntil; import com.google.common.collect.ImmutableMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * */ @Component public class PluginSystemManager { private final PluginController pluginController; private final PluginAccessor pluginAccessor; private final SpeakeasyData data; private final ProductAccessor productAccessor; private static final Logger log = LoggerFactory.getLogger(PluginSystemManager.class); private final Map<PluginType,PluginTypeHandler> typeHandlers; @Autowired public PluginSystemManager(PluginController pluginController, PluginAccessor pluginAccessor, SpeakeasyData data, ProductAccessor productAccessor, JarPluginTypeHandler jarPluginTypeHandler, ZipPluginTypeHandler zipPluginTypeHandler, XmlPluginTypeHandler xmlPluginTypeHandler) { this.pluginController = pluginController; this.pluginAccessor = pluginAccessor; this.data = data; this.productAccessor = productAccessor; this.typeHandlers = ImmutableMap.of( PluginType.JAR, jarPluginTypeHandler, PluginType.ZIP, zipPluginTypeHandler, PluginType.XML, xmlPluginTypeHandler ); } public String install(File pluginFile, String expectedPluginKey, String user) throws PluginOperationFailedException { PluginArtifact pluginArtifact = null; String pluginKey = null; for (PluginTypeHandler handler : typeHandlers.values()) { pluginKey = handler.canInstall(pluginFile); if (pluginKey != null) { if (expectedPluginKey != null && !pluginKey.equals(expectedPluginKey)) { throw new PluginOperationFailedException("Unable to install plugin file "+ "because the expected plugin key, " + expectedPluginKey + ", is different than the one in the file - " + pluginKey, expectedPluginKey); } String recordedAuthor = data.getPluginAuthor(pluginKey); if (pluginAccessor.getPlugin(pluginKey) != null && !user.equals(recordedAuthor)) { throw new PluginOperationFailedException("Unable to upgrade the '" + pluginKey + "' as you didn't install it", pluginKey); } pluginArtifact = handler.createArtifact(pluginFile); break; } } if (pluginArtifact == null || pluginKey == null) { throw new PluginOperationFailedException("Unable to handle plugin file " + pluginFile.toString() + ", likely due to an invalid plugin key", null); } data.setPluginAuthor(pluginKey, user); Set<String> pluginKeys = pluginController.installPlugins(pluginArtifact); if (pluginKeys.size() == 1) { final String installedKey = pluginKeys.iterator().next(); final Plugin plugin = pluginAccessor.getPlugin(installedKey); WaitUntil.invoke(new WaitUntil.WaitCondition() { public boolean isFinished() { for (ModuleDescriptor desc : plugin.getModuleDescriptors()) { if (!pluginAccessor.isPluginModuleEnabled(desc.getCompleteKey()) && desc instanceof UnrecognisedModuleDescriptor) { return false; } } return true; } public String getWaitMessage() { return "Waiting for all module descriptors to be resolved and enabled"; } }); if (!pluginAccessor.isPluginEnabled(plugin.getKey())) { String cause = "Plugin didn't install correctly"; for (ModuleDescriptor descriptor : plugin.getModuleDescriptors()) { if (descriptor instanceof UnloadableModuleDescriptor) { cause = ((UnloadableModuleDescriptor)descriptor).getErrorText(); break; } } throw new PluginOperationFailedException(cause, plugin.getKey()); } else { for (ModuleDescriptor descriptor : plugin.getModuleDescriptors()) { if (descriptor instanceof CommonJsModulesDescriptor) { Set<String> unresolved = ((CommonJsModulesDescriptor)descriptor).getUnresolvedExternalModuleDependencies(); if (!unresolved.isEmpty()) { throw new PluginOperationFailedException("Plugin didn't install due to missing modules: " + unresolved, plugin.getKey()); } } } } return plugin.getKey(); } else { throw new PluginOperationFailedException("Plugin didn't install correctly", null); } } public void uninstall(String pluginKey, String user) throws PluginOperationFailedException { Plugin plugin = pluginAccessor.getPlugin(pluginKey); if (user.equals(data.getPluginAuthor(pluginKey))) { pluginController.uninstall(plugin); data.clearPluginAuthor(pluginKey); } else { throw new PluginOperationFailedException("User '" + user + "' is not the author of plugin '" + pluginKey + "' and cannot uninstall it", pluginKey); } } public File getPluginAsProject(final String pluginKey, final PluginType pluginType, final String user) { final Plugin plugin = pluginAccessor.getPlugin(pluginKey); final Map<String,Object> context = new HashMap<String,Object>() {{ put("pluginKey", plugin.getKey()); put("user", sanitizeUser(user)); put("version", plugin.getPluginInformation().getVersion()); put("author", data.getPluginAuthor(plugin.getKey())); put("product", productAccessor.getSdkName()); put("productVersion", productAccessor.getVersion()); put("productDataVersion", productAccessor.getDataVersion()); put("speakeasyVersion", data.getSpeakeasyVersion()); }}; return typeHandlers.get(pluginType).getPluginAsProject(pluginKey, context); } public File getPluginArtifact(String pluginKey, PluginType pluginType) { try { return typeHandlers.get(pluginType).getPluginArtifact(pluginKey); } catch (IOException e) { throw new RuntimeException("Unable to create plugin project", e); } } public List<String> getPluginFileNames(String pluginKey, PluginType pluginType) { return typeHandlers.get(pluginType).getPluginFileNames(pluginKey); } private String sanitizeUser(String user) { return user.replace("@", "at"); } public String getPluginFile(String pluginKey, PluginType type, String fileName) { try { return typeHandlers.get(type).getPluginFile(pluginKey, fileName); } catch (IOException e) { throw new IllegalArgumentException(e); } } public String saveAndRebuild(String pluginKey, PluginType pluginType, String fileName, String contents, String user) throws PluginOperationFailedException { try { File tmpFile = typeHandlers.get(pluginType).rebuildPlugin(pluginKey, fileName, contents); return install(tmpFile, pluginKey, user); } catch (IOException e) { e.printStackTrace(); throw new PluginOperationFailedException("Unable to create extension file: " + e.getMessage(), e, pluginKey); } } public String forkAndInstall(String pluginKey, String forkPluginKey, PluginType pluginType, String user, String description) throws PluginOperationFailedException { if (pluginKey.contains("-fork-")) { throw new PluginOperationFailedException("Cannot fork an existing fork", pluginKey); } try { File forkFile = typeHandlers.get(pluginType).createFork(pluginKey, forkPluginKey, user, description); return install(forkFile, forkPluginKey, user); } catch (IOException e) { log.error(e.getMessage(), e); throw new PluginOperationFailedException("Unable to create forked plugin jar", e, pluginKey); } catch (RuntimeException e) { log.error(e.getMessage(), e); throw new PluginOperationFailedException("Unable transform plugin descriptor xml", e, pluginKey); } } public String createExtension(PluginType pluginType, String pluginKey, String remoteUser, String description, String name) { final PluginTypeHandler typeHandler = typeHandlers.get(pluginType); try { File tmpFile = typeHandler.createExample(pluginKey, name, description); return install(tmpFile, pluginKey, remoteUser); } catch (IOException e) { throw new PluginOperationFailedException("Unable to create extension", e, pluginKey); } } }