001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.server.plugins; 021 022 import com.google.common.collect.Lists; 023 import com.google.common.collect.Maps; 024 import org.apache.commons.io.FileUtils; 025 import org.apache.commons.lang.StringUtils; 026 import org.slf4j.Logger; 027 import org.slf4j.LoggerFactory; 028 import org.sonar.api.ServerComponent; 029 import org.sonar.api.platform.PluginMetadata; 030 import org.sonar.api.utils.Logs; 031 import org.sonar.api.utils.SonarException; 032 import org.sonar.api.utils.TimeProfiler; 033 import org.sonar.core.plugins.DefaultPluginMetadata; 034 import org.sonar.core.plugins.PluginFileExtractor; 035 import org.sonar.server.platform.DefaultServerFileSystem; 036 import org.sonar.server.platform.ServerStartException; 037 038 import java.io.File; 039 import java.io.IOException; 040 import java.util.Collection; 041 import java.util.List; 042 import java.util.Map; 043 044 public class PluginDeployer implements ServerComponent { 045 046 private static final Logger LOG = LoggerFactory.getLogger(PluginDeployer.class); 047 048 private DefaultServerFileSystem fileSystem; 049 private Map<String, PluginMetadata> pluginByKeys = Maps.newHashMap(); 050 private PluginFileExtractor extractor; 051 052 public PluginDeployer(DefaultServerFileSystem fileSystem) { 053 this(fileSystem, new PluginFileExtractor()); 054 } 055 056 PluginDeployer(DefaultServerFileSystem fileSystem, PluginFileExtractor extractor) { 057 this.fileSystem = fileSystem; 058 this.extractor = extractor; 059 } 060 061 public void start() throws IOException { 062 TimeProfiler profiler = new TimeProfiler().start("Install plugins"); 063 064 deleteUninstalledPlugins(); 065 066 loadUserPlugins(); 067 moveAndLoadDownloadedPlugins(); 068 loadCorePlugins(); 069 070 deployPlugins(); 071 072 profiler.stop(); 073 } 074 075 private void deleteUninstalledPlugins() { 076 File trashDir = fileSystem.getRemovedPluginsDir(); 077 try { 078 if (trashDir.exists()) { 079 FileUtils.deleteDirectory(trashDir); 080 } 081 } catch (IOException e) { 082 throw new SonarException("Fail to clean the plugin trash directory: " + trashDir, e); 083 } 084 } 085 086 private void loadUserPlugins() throws IOException { 087 for (File file : fileSystem.getUserPlugins()) { 088 registerPlugin(file, false, false); 089 } 090 } 091 092 private void registerPlugin(File file, boolean isCore, boolean canDelete) throws IOException { 093 DefaultPluginMetadata metadata = extractor.extractMetadata(file, isCore); 094 if (StringUtils.isNotBlank(metadata.getKey())) { 095 PluginMetadata existing = pluginByKeys.get(metadata.getKey()); 096 if (existing != null) { 097 if (canDelete) { 098 FileUtils.deleteQuietly(existing.getFile()); 099 Logs.INFO.info("Plugin " + metadata.getKey() + " replaced by new version"); 100 101 } else { 102 throw new ServerStartException("Found two plugins with the same key '" + metadata.getKey() + "': " + metadata.getFile().getName() + " and " 103 + existing.getFile().getName()); 104 } 105 } 106 pluginByKeys.put(metadata.getKey(), metadata); 107 } 108 } 109 110 private void moveAndLoadDownloadedPlugins() throws IOException { 111 if (fileSystem.getDownloadedPluginsDir().exists()) { 112 Collection<File> jars = FileUtils.listFiles(fileSystem.getDownloadedPluginsDir(), new String[]{"jar"}, false); 113 for (File jar : jars) { 114 File movedJar = moveDownloadedFile(jar); 115 if (movedJar != null) { 116 registerPlugin(movedJar, false, true); 117 } 118 } 119 } 120 } 121 122 private File moveDownloadedFile(File jar) { 123 File destDir = fileSystem.getUserPluginsDir(); 124 File destFile = new File(destDir, jar.getName()); 125 if (destFile.exists()) { 126 // plugin with same filename already installed 127 FileUtils.deleteQuietly(jar); 128 return null; 129 } 130 try { 131 FileUtils.moveFileToDirectory(jar, destDir, true); 132 return destFile; 133 134 } catch (IOException e) { 135 LOG.error("Fail to move the downloaded file: " + jar.getAbsolutePath(), e); 136 return null; 137 } 138 } 139 140 private void loadCorePlugins() throws IOException { 141 for (File file : fileSystem.getCorePlugins()) { 142 registerPlugin(file, true, false); 143 } 144 } 145 146 public void uninstall(String pluginKey) { 147 PluginMetadata metadata = pluginByKeys.get(pluginKey); 148 if (metadata != null && !metadata.isCore()) { 149 try { 150 File masterFile = new File(fileSystem.getUserPluginsDir(), metadata.getFile().getName()); 151 FileUtils.moveFileToDirectory(masterFile, fileSystem.getRemovedPluginsDir(), true); 152 } catch (IOException e) { 153 throw new SonarException("Fail to uninstall plugin: " + pluginKey, e); 154 } 155 } 156 } 157 158 public List<String> getUninstalls() { 159 List<String> names = Lists.newArrayList(); 160 if (fileSystem.getRemovedPluginsDir().exists()) { 161 List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[]{"jar"}, false); 162 for (File file : files) { 163 names.add(file.getName()); 164 } 165 } 166 return names; 167 } 168 169 public void cancelUninstalls() { 170 if (fileSystem.getRemovedPluginsDir().exists()) { 171 List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[]{"jar"}, false); 172 for (File file : files) { 173 try { 174 FileUtils.moveFileToDirectory(file, fileSystem.getUserPluginsDir(), false); 175 } catch (IOException e) { 176 throw new SonarException("Fail to cancel plugin uninstalls", e); 177 } 178 } 179 } 180 } 181 182 private void deployPlugins() { 183 for (PluginMetadata metadata : pluginByKeys.values()) { 184 deploy((DefaultPluginMetadata) metadata); 185 } 186 } 187 188 private void deploy(DefaultPluginMetadata plugin) { 189 try { 190 LOG.debug("Deploy plugin " + plugin); 191 192 File pluginDeployDir = new File(fileSystem.getDeployedPluginsDir(), plugin.getKey()); 193 FileUtils.forceMkdir(pluginDeployDir); 194 FileUtils.cleanDirectory(pluginDeployDir); 195 196 List<File> deprecatedExtensions = fileSystem.getExtensions(plugin.getKey()); 197 for (File deprecatedExtension : deprecatedExtensions) { 198 plugin.addDeprecatedExtension(deprecatedExtension); 199 } 200 201 extractor.install(plugin, pluginDeployDir); 202 203 } catch (IOException e) { 204 throw new RuntimeException("Fail to deploy the plugin " + plugin, e); 205 } 206 } 207 208 public Collection<PluginMetadata> getMetadata() { 209 return pluginByKeys.values(); 210 } 211 212 public PluginMetadata getMetadata(String pluginKey) { 213 return pluginByKeys.get(pluginKey); 214 } 215 }