001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube 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     * SonarQube 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 License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019     */
020    package org.sonar.batch.bootstrap;
021    
022    import com.google.common.base.Joiner;
023    import com.google.common.base.Splitter;
024    import com.google.common.collect.Lists;
025    import com.google.common.collect.Maps;
026    import org.apache.commons.lang.StringUtils;
027    import org.slf4j.Logger;
028    import org.slf4j.LoggerFactory;
029    import org.sonar.api.CoreProperties;
030    import org.sonar.api.Plugin;
031    import org.sonar.api.SonarPlugin;
032    import org.sonar.api.config.Settings;
033    import org.sonar.api.platform.PluginMetadata;
034    import org.sonar.api.platform.PluginRepository;
035    import org.sonar.core.plugins.PluginClassloaders;
036    import org.sonar.core.plugins.RemotePlugin;
037    
038    import java.io.File;
039    import java.text.MessageFormat;
040    import java.util.Arrays;
041    import java.util.Collection;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.Set;
045    
046    import static com.google.common.collect.Lists.newArrayList;
047    import static com.google.common.collect.Sets.newHashSet;
048    
049    public class BatchPluginRepository implements PluginRepository {
050    
051      private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class);
052      private static final String CORE_PLUGIN = "core";
053    
054      private PluginsReferential pluginsReferential;
055      private Map<String, Plugin> pluginsByKey;
056      private Map<String, PluginMetadata> metadataByKey;
057      private Settings settings;
058      private PluginClassloaders classLoaders;
059      private final AnalysisMode analysisMode;
060      private final BatchPluginJarInstaller pluginInstaller;
061    
062      public BatchPluginRepository(PluginsReferential pluginsReferential, Settings settings, AnalysisMode analysisMode,
063        BatchPluginJarInstaller pluginInstaller) {
064        this.pluginsReferential = pluginsReferential;
065        this.settings = settings;
066        this.analysisMode = analysisMode;
067        this.pluginInstaller = pluginInstaller;
068      }
069    
070      public void start() {
071        LOG.info("Install plugins");
072        doStart(pluginsReferential.pluginList());
073    
074        Map<PluginMetadata, SonarPlugin> localPlugins = pluginsReferential.localPlugins();
075        if (!localPlugins.isEmpty()) {
076          LOG.info("Install local plugins");
077          for (Map.Entry<PluginMetadata, SonarPlugin> pluginByMetadata : localPlugins.entrySet()) {
078            metadataByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getKey());
079            pluginsByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getValue());
080          }
081        }
082      }
083    
084      void doStart(List<RemotePlugin> remotePlugins) {
085        PluginFilter filter = new PluginFilter(settings, analysisMode);
086        metadataByKey = Maps.newHashMap();
087        for (RemotePlugin remote : remotePlugins) {
088          if (filter.accepts(remote.getKey())) {
089            File pluginFile = pluginsReferential.pluginFile(remote);
090            PluginMetadata metadata = pluginInstaller.installToCache(pluginFile, remote.isCore());
091            if (StringUtils.isBlank(metadata.getBasePlugin()) || filter.accepts(metadata.getBasePlugin())) {
092              metadataByKey.put(metadata.getKey(), metadata);
093            } else {
094              LOG.debug("Excluded plugin: " + metadata.getKey());
095            }
096          }
097        }
098        classLoaders = new PluginClassloaders(Thread.currentThread().getContextClassLoader());
099        pluginsByKey = classLoaders.init(metadataByKey.values());
100      }
101    
102      public void stop() {
103        if (classLoaders != null) {
104          classLoaders.clean();
105          classLoaders = null;
106        }
107      }
108    
109      public Plugin getPlugin(String key) {
110        return pluginsByKey.get(key);
111      }
112    
113      public Collection<PluginMetadata> getMetadata() {
114        return metadataByKey.values();
115      }
116    
117      public PluginMetadata getMetadata(String pluginKey) {
118        return metadataByKey.get(pluginKey);
119      }
120    
121      public Map<PluginMetadata, Plugin> getPluginsByMetadata() {
122        Map<PluginMetadata, Plugin> result = Maps.newHashMap();
123        for (Map.Entry<String, PluginMetadata> entry : metadataByKey.entrySet()) {
124          String pluginKey = entry.getKey();
125          PluginMetadata metadata = entry.getValue();
126          result.put(metadata, pluginsByKey.get(pluginKey));
127        }
128        return result;
129      }
130    
131      static class PluginFilter {
132        private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead.";
133        Set<String> whites = newHashSet(), blacks = newHashSet();
134    
135        PluginFilter(Settings settings, AnalysisMode mode) {
136          if (settings.hasKey(CoreProperties.BATCH_INCLUDE_PLUGINS)) {
137            whites.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS)));
138          }
139          if (settings.hasKey(CoreProperties.BATCH_EXCLUDE_PLUGINS)) {
140            blacks.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS)));
141          }
142          if (mode.isPreview()) {
143            // These default values are not supported by Settings because the class CorePlugin
144            // is not loaded yet.
145            if (settings.hasKey(CoreProperties.DRY_RUN_INCLUDE_PLUGINS)) {
146              LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS));
147              whites.addAll(propertyValues(settings,
148                CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE));
149            } else {
150              whites.addAll(propertyValues(settings,
151                CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE));
152            }
153            if (settings.hasKey(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS)) {
154              LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS));
155              blacks.addAll(propertyValues(settings,
156                CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE));
157            } else {
158              blacks.addAll(propertyValues(settings,
159                CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE));
160            }
161          }
162          if (!whites.isEmpty()) {
163            LOG.info("Include plugins: " + Joiner.on(", ").join(whites));
164          }
165          if (!blacks.isEmpty()) {
166            LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks));
167          }
168        }
169    
170        static List<String> propertyValues(Settings settings, String key, String defaultValue) {
171          String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue);
172          return Lists.newArrayList(Splitter.on(",").trimResults().split(s));
173        }
174    
175        boolean accepts(String pluginKey) {
176          if (CORE_PLUGIN.equals(pluginKey)) {
177            return true;
178          }
179    
180          List<String> mergeList = newArrayList(blacks);
181          mergeList.removeAll(whites);
182          return mergeList.isEmpty() || !mergeList.contains(pluginKey);
183        }
184      }
185    }