001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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    }