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 */
020package org.sonar.server.plugins;
021
022import com.google.common.collect.Lists;
023import com.google.common.collect.Maps;
024import org.apache.commons.io.FileUtils;
025import org.apache.commons.lang.StringUtils;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028import org.sonar.api.ServerComponent;
029import org.sonar.api.platform.PluginMetadata;
030import org.sonar.api.utils.Logs;
031import org.sonar.api.utils.SonarException;
032import org.sonar.api.utils.TimeProfiler;
033import org.sonar.core.plugins.DefaultPluginMetadata;
034import org.sonar.core.plugins.PluginInstaller;
035import org.sonar.server.platform.DefaultServerFileSystem;
036import org.sonar.server.platform.ServerStartException;
037
038import java.io.File;
039import java.io.IOException;
040import java.util.Collection;
041import java.util.List;
042import java.util.Map;
043
044public class PluginDeployer implements ServerComponent {
045
046  private static final Logger LOG = LoggerFactory.getLogger(PluginDeployer.class);
047
048  private final DefaultServerFileSystem fileSystem;
049  private final Map<String, PluginMetadata> pluginByKeys = Maps.newHashMap();
050  private final 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() {
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() {
087    for (File file : fileSystem.getUserPlugins()) {
088      registerPlugin(file, false, false);
089    }
090  }
091
092  private void registerPlugin(File file, boolean isCore, boolean canDelete) {
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() {
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() {
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}