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.PluginInstaller;
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 PluginInstaller installer;
051
052 public PluginDeployer(DefaultServerFileSystem fileSystem) {
053 this(fileSystem, new PluginInstaller());
054 }
055
056 PluginDeployer(DefaultServerFileSystem fileSystem, PluginInstaller installer) {
057 this.fileSystem = fileSystem;
058 this.installer = installer;
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 = installer.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 installer.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 }