From 24d686a58de32655ab4a324b26075f6d226767a3 Mon Sep 17 00:00:00 2001
From: Teryk Bellahsene <teryk.bellahsene@sonarsource.com>
Date: Fri, 17 Jul 2015 14:29:22 +0200
Subject: SONAR-6376 ws plugins/installed and plugins/pending add category
 field

- use Optional when retrieving an UpdateCenter
---
 .../sonar/server/platform/ws/UpgradesAction.java   |  21 ++--
 .../org/sonar/server/plugins/PluginDownloader.java |  37 +++---
 .../sonar/server/plugins/UpdateCenterClient.java   |  24 ++--
 .../server/plugins/UpdateCenterMatrixFactory.java  |  16 +--
 .../sonar/server/plugins/ws/AvailableAction.java   |  12 +-
 .../org/sonar/server/plugins/ws/InstallAction.java |  18 ++-
 .../sonar/server/plugins/ws/InstalledAction.java   |  24 ++--
 .../org/sonar/server/plugins/ws/PendingAction.java |  44 +++----
 .../sonar/server/plugins/ws/PluginWSCommons.java   | 133 +++++++++++----------
 .../org/sonar/server/plugins/ws/UpdateAction.java  |  16 ++-
 .../org/sonar/server/plugins/ws/UpdatesAction.java |  24 ++--
 .../main/java/org/sonar/server/ui/JRubyFacade.java |   2 +-
 .../server/platform/ws/UpgradesActionTest.java     |  14 ++-
 .../sonar/server/plugins/PluginDownloaderTest.java |  20 +++-
 .../server/plugins/UpdateCenterClientTest.java     |  45 ++++---
 ...stractUpdateCenterBasedPluginsWsActionTest.java |   3 +-
 .../server/plugins/ws/AvailableActionTest.java     |  20 +++-
 .../sonar/server/plugins/ws/InstallActionTest.java |  15 ++-
 .../server/plugins/ws/InstalledActionTest.java     |  34 +++++-
 .../sonar/server/plugins/ws/PendingActionTest.java |  32 ++++-
 .../server/plugins/ws/PluginWSCommonsTest.java     |   9 +-
 .../sonar/server/plugins/ws/UpdateActionTest.java  |  15 ++-
 .../sonar/core/platform/PluginInfoFunctions.java   |  57 +++++++++
 23 files changed, 426 insertions(+), 209 deletions(-)
 create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/PluginInfoFunctions.java

diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
index 8e2edabaa89..4a77e6a6d26 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.platform.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.io.Resources;
 import java.util.List;
 import org.sonar.api.server.ws.Request;
@@ -82,18 +83,22 @@ public class UpgradesAction implements SystemWsAction {
   private void writeResponse(JsonWriter jsonWriter) {
     jsonWriter.beginObject();
 
-    UpdateCenter updateCenter = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
+    Optional<UpdateCenter> updateCenter = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
     writeUpgrades(jsonWriter, updateCenter);
-    jsonWriter.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.getDate());
+    if (updateCenter.isPresent()) {
+      jsonWriter.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.get().getDate());
+    }
 
     jsonWriter.endObject();
   }
 
-  private void writeUpgrades(JsonWriter jsonWriter, UpdateCenter updateCenter) {
+  private void writeUpgrades(JsonWriter jsonWriter, Optional<UpdateCenter> updateCenter) {
     jsonWriter.name(ARRAY_UPGRADES).beginArray();
 
-    for (SonarUpdate sonarUpdate : updateCenter.findSonarUpdates()) {
-      writeUpgrade(jsonWriter, sonarUpdate);
+    if (updateCenter.isPresent()) {
+      for (SonarUpdate sonarUpdate : updateCenter.get().findSonarUpdates()) {
+        writeUpgrade(jsonWriter, sonarUpdate);
+      }
     }
 
     jsonWriter.endArray();
@@ -132,7 +137,7 @@ public class UpgradesAction implements SystemWsAction {
     for (Release release : pluginsToUpgrade) {
       jsonWriter.beginObject();
 
-      pluginWSCommons.writeMetadata(jsonWriter, (Plugin) release.getArtifact());
+      pluginWSCommons.writePlugin(jsonWriter, (Plugin) release.getArtifact());
       jsonWriter.prop(PROPERTY_VERSION, release.getVersion().toString());
 
       jsonWriter.endObject();
@@ -146,9 +151,7 @@ public class UpgradesAction implements SystemWsAction {
 
     for (Plugin incompatiblePlugin : incompatiblePlugins) {
       jsonWriter.beginObject();
-
-      pluginWSCommons.writeMetadata(jsonWriter, incompatiblePlugin);
-
+      pluginWSCommons.writePlugin(jsonWriter, incompatiblePlugin);
       jsonWriter.endObject();
     }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
index 96fec742509..a164ad7bc18 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
@@ -19,6 +19,14 @@
  */
 package org.sonar.server.plugins;
 
+import com.google.common.base.Optional;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import org.apache.commons.io.FileUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.utils.HttpDownloader;
@@ -28,16 +36,9 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.platform.DefaultServerFileSystem;
 import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Lists.newArrayList;
 import static org.apache.commons.io.FileUtils.cleanDirectory;
@@ -120,15 +121,17 @@ public class PluginDownloader implements Startable {
   }
 
   public void download(String pluginKey, Version version) {
-    for (Release release : updateCenterMatrixFactory.getUpdateCenter(true).findInstallablePlugins(pluginKey, version)) {
-      try {
-        downloadRelease(release);
-
-      } catch (Exception e) {
-        String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
-          release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
-        LOG.debug(message, e);
-        throw new SonarException(message, e);
+    Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
+    if (updateCenter.isPresent()) {
+      for (Release release : updateCenter.get().findInstallablePlugins(pluginKey, version)) {
+        try {
+          downloadRelease(release);
+        } catch (Exception e) {
+          String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
+            release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
+          LOG.debug(message, e);
+          throw new SonarException(message, e);
+        }
       }
     }
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
index d4b3d164890..6d94cdeaa26 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
@@ -19,6 +19,12 @@
  */
 package org.sonar.server.plugins;
 
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
 import org.apache.commons.io.IOUtils;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
@@ -30,12 +36,6 @@ import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.UpdateCenterDeserializer;
 import org.sonar.updatecenter.common.UpdateCenterDeserializer.Mode;
 
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.util.Date;
-
 /**
  * HTTP client to load data from the remote update center hosted at http://update.sonarsource.org.
  *
@@ -68,25 +68,31 @@ public class UpdateCenterClient {
 
   private final URI uri;
   private final UriReader uriReader;
+  private final boolean isActivated;
   private UpdateCenter pluginCenter = null;
   private long lastRefreshDate = 0;
 
   public UpdateCenterClient(UriReader uriReader, Settings settings) throws URISyntaxException {
     this.uriReader = uriReader;
     this.uri = new URI(settings.getString(URL_PROPERTY));
+    this.isActivated = settings.getBoolean(ACTIVATION_PROPERTY);
     Loggers.get(getClass()).info("Update center: " + uriReader.description(uri));
   }
 
-  public UpdateCenter getUpdateCenter() {
+  public Optional<UpdateCenter> getUpdateCenter() {
     return getUpdateCenter(false);
   }
 
-  public UpdateCenter getUpdateCenter(boolean forceRefresh) {
+  public Optional<UpdateCenter> getUpdateCenter(boolean forceRefresh) {
+    if (!isActivated) {
+      return Optional.absent();
+    }
+
     if (pluginCenter == null || forceRefresh || needsRefresh()) {
       pluginCenter = init();
       lastRefreshDate = System.currentTimeMillis();
     }
-    return pluginCenter;
+    return Optional.fromNullable(pluginCenter);
   }
 
   public Date getLastRefreshDate() {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
index b793b88cf77..6ef445f1287 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins;
 
+import com.google.common.base.Optional;
 import org.sonar.api.platform.Server;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
@@ -33,20 +34,19 @@ public class UpdateCenterMatrixFactory {
   private final InstalledPluginReferentialFactory installedPluginReferentialFactory;
 
   public UpdateCenterMatrixFactory(UpdateCenterClient centerClient, Server server,
-                                   InstalledPluginReferentialFactory installedPluginReferentialFactory) {
+    InstalledPluginReferentialFactory installedPluginReferentialFactory) {
     this.centerClient = centerClient;
     this.installedPluginReferentialFactory = installedPluginReferentialFactory;
     this.sonarVersion = Version.create(server.getVersion());
   }
 
-  public UpdateCenter getUpdateCenter(boolean refreshUpdateCenter) {
-    UpdateCenter updatePluginCenter = centerClient.getUpdateCenter(refreshUpdateCenter);
-    if (updatePluginCenter != null) {
-      return updatePluginCenter.setInstalledSonarVersion(sonarVersion).registerInstalledPlugins(
+  public Optional<UpdateCenter> getUpdateCenter(boolean refreshUpdateCenter) {
+    Optional<UpdateCenter> updatePluginCenter = centerClient.getUpdateCenter(refreshUpdateCenter);
+    if (updatePluginCenter.isPresent()) {
+      return Optional.of(updatePluginCenter.get().setInstalledSonarVersion(sonarVersion).registerInstalledPlugins(
         installedPluginReferentialFactory.getInstalledPluginReferential())
-        .setDate(centerClient.getLastRefreshDate());
+        .setDate(centerClient.getLastRefreshDate()));
     }
-    return null;
+    return Optional.absent();
   }
 }
-
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
index 1b4df7beccd..3ae4784eb91 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.io.Resources;
 import java.util.Collection;
@@ -69,20 +70,21 @@ public class AvailableAction implements PluginsWsAction {
     JsonWriter jsonWriter = response.newJsonWriter();
     jsonWriter.beginObject();
 
-    UpdateCenter updateCenter = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
+    Optional<UpdateCenter> updateCenter = updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
 
     writePlugins(jsonWriter, updateCenter);
-
     pluginWSCommons.writeUpdateCenterProperties(jsonWriter, updateCenter);
 
     jsonWriter.endObject();
     jsonWriter.close();
   }
 
-  private void writePlugins(JsonWriter jsonWriter, UpdateCenter updateCenter) {
+  private void writePlugins(JsonWriter jsonWriter, Optional<UpdateCenter> updateCenter) {
     jsonWriter.name(ARRAY_PLUGINS).beginArray();
-    for (PluginUpdate pluginUpdate : retrieveAvailablePlugins(updateCenter)) {
-      pluginWSCommons.writePluginUpdate(jsonWriter, pluginUpdate);
+    if (updateCenter.isPresent()) {
+      for (PluginUpdate pluginUpdate : retrieveAvailablePlugins(updateCenter.get())) {
+        pluginWSCommons.writePluginUpdate(jsonWriter, pluginUpdate);
+      }
     }
     jsonWriter.endArray();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstallAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
index e629620df2e..32aab6c6279 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import javax.annotation.Nullable;
@@ -30,6 +31,7 @@ import org.sonar.server.plugins.PluginDownloader;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.user.UserSession;
 import org.sonar.updatecenter.common.PluginUpdate;
+import org.sonar.updatecenter.common.UpdateCenter;
 
 import static java.lang.String.format;
 
@@ -77,15 +79,21 @@ public class InstallAction implements PluginsWsAction {
   }
 
   private PluginUpdate findAvailablePluginByKey(String key) {
-    PluginUpdate pluginUpdate = Iterables.find(
-      updateCenterFactory.getUpdateCenter(false).findAvailablePlugins(),
-      hasKey(key),
-      MISSING_PLUGIN
-      );
+    PluginUpdate pluginUpdate = MISSING_PLUGIN;
+
+    Optional<UpdateCenter> updateCenter = updateCenterFactory.getUpdateCenter(false);
+    if (updateCenter.isPresent()) {
+      pluginUpdate= Iterables.find(
+        updateCenter.get().findAvailablePlugins(),
+        hasKey(key),
+        MISSING_PLUGIN);
+    }
+
     if (pluginUpdate == MISSING_PLUGIN) {
       throw new IllegalArgumentException(
         format("No plugin with key '%s' or plugin '%s' is already installed in latest version", key, key));
     }
+
     return pluginUpdate;
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
index 37cc7436ec4..cf7998c71c4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.io.Resources;
 import java.util.Collection;
 import java.util.SortedSet;
@@ -28,9 +29,12 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Plugin;
 
 import static com.google.common.collect.ImmutableSortedSet.copyOf;
 import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR;
+import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey;
 
 /**
  * Implementation of the {@code installed} action for the Plugins WebService.
@@ -40,10 +44,12 @@ public class InstalledAction implements PluginsWsAction {
 
   private final ServerPluginRepository pluginRepository;
   private final PluginWSCommons pluginWSCommons;
+  private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
 
-  public InstalledAction(ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons) {
+  public InstalledAction(ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
     this.pluginRepository = pluginRepository;
     this.pluginWSCommons = pluginWSCommons;
+    this.updateCenterMatrixFactory = updateCenterMatrixFactory;
   }
 
   @Override
@@ -57,28 +63,24 @@ public class InstalledAction implements PluginsWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    Collection<PluginInfo> infos = retrieveAndSortPluginMetadata();
+    Collection<PluginInfo> pluginInfoList = searchPluginInfoList();
 
     JsonWriter jsonWriter = response.newJsonWriter();
     jsonWriter.setSerializeEmptys(false);
     jsonWriter.beginObject();
 
-    writeMetadataList(jsonWriter, infos);
+    writePluginInfoList(jsonWriter, pluginInfoList);
 
     jsonWriter.endObject();
     jsonWriter.close();
   }
 
-  private SortedSet<PluginInfo> retrieveAndSortPluginMetadata() {
+  private SortedSet<PluginInfo> searchPluginInfoList() {
     return copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, pluginRepository.getPluginInfos());
   }
 
-  private void writeMetadataList(JsonWriter jsonWriter, Collection<PluginInfo> pluginMetadatas) {
-    jsonWriter.name(ARRAY_PLUGINS);
-    jsonWriter.beginArray();
-    for (PluginInfo pluginMetadata : pluginMetadatas) {
-      pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
-    }
-    jsonWriter.endArray();
+  private void writePluginInfoList(JsonWriter jsonWriter, Collection<PluginInfo> pluginInfoList) {
+    ImmutableMap<String, Plugin> compatiblesPluginsByKeys = compatiblePluginsByKey(updateCenterMatrixFactory);
+    pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsByKeys, ARRAY_PLUGINS);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
index 86293e731d5..c7ea6d33a29 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
@@ -19,6 +19,9 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.Map;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -26,12 +29,11 @@ import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.plugins.PluginDownloader;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Plugin;
 
-import java.util.Collection;
-
-import static com.google.common.collect.ImmutableSortedSet.copyOf;
 import static com.google.common.io.Resources.getResource;
-import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR;
+import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey;
 
 /**
  * Implementation of the {@code pending} action for the Plugins WebService.
@@ -44,13 +46,15 @@ public class PendingAction implements PluginsWsAction {
   private final PluginDownloader pluginDownloader;
   private final ServerPluginRepository installer;
   private final PluginWSCommons pluginWSCommons;
+  private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
 
   public PendingAction(PluginDownloader pluginDownloader,
-                       ServerPluginRepository installer,
-                       PluginWSCommons pluginWSCommons) {
+    ServerPluginRepository installer,
+    PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
     this.pluginDownloader = pluginDownloader;
     this.installer = installer;
     this.pluginWSCommons = pluginWSCommons;
+    this.updateCenterMatrixFactory = updateCenterMatrixFactory;
   }
 
   @Override
@@ -64,36 +68,24 @@ public class PendingAction implements PluginsWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
+    ImmutableMap<String, Plugin> compatiblePluginsByKey = compatiblePluginsByKey(updateCenterMatrixFactory);
+
     JsonWriter jsonWriter = response.newJsonWriter();
 
     jsonWriter.beginObject();
-
-    writeInstalling(jsonWriter);
-
-    writeRemoving(jsonWriter);
-
+    writeInstalling(jsonWriter, compatiblePluginsByKey);
+    writeRemoving(jsonWriter, compatiblePluginsByKey);
     jsonWriter.endObject();
     jsonWriter.close();
   }
 
-  private void writeInstalling(JsonWriter jsonWriter) {
-    jsonWriter.name(ARRAY_INSTALLING);
-    jsonWriter.beginArray();
+  private void writeInstalling(JsonWriter json, Map<String, Plugin> compatiblePluginsByKey) {
     Collection<PluginInfo> plugins = pluginDownloader.getDownloadedPlugins();
-    for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
-      pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
-    }
-    jsonWriter.endArray();
+    pluginWSCommons.writePluginInfoList(json, plugins, compatiblePluginsByKey, ARRAY_INSTALLING);
   }
 
-  private void writeRemoving(JsonWriter jsonWriter) {
-    jsonWriter.name(ARRAY_REMOVING);
-    jsonWriter.beginArray();
+  private void writeRemoving(JsonWriter json, Map<String, Plugin> compatiblePluginsByKey) {
     Collection<PluginInfo> plugins = installer.getUninstalledPlugins();
-    for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
-      pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
-    }
-    jsonWriter.endArray();
+    pluginWSCommons.writePluginInfoList(json, plugins, compatiblePluginsByKey, ARRAY_REMOVING);
   }
-
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
index 6c764861e38..209df136186 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
@@ -21,11 +21,19 @@ package org.sonar.server.plugins.ws;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.updatecenter.common.Artifact;
 import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.PluginUpdate;
@@ -33,9 +41,12 @@ import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
+import static com.google.common.collect.ImmutableSortedSet.copyOf;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
 import static java.lang.String.CASE_INSENSITIVE_ORDER;
+import static org.sonar.core.platform.PluginInfoFunctions.toKey;
+import static org.sonar.core.platform.PluginInfoFunctions.toName;
 
 public class PluginWSCommons {
   static final String PROPERTY_KEY = "key";
@@ -61,54 +72,48 @@ public class PluginWSCommons {
   static final String PROPERTY_CHANGE_LOG_URL = "changeLogUrl";
 
   public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
-    .onResultOf(PluginMetadataToName.INSTANCE)
-    .compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE));
+    .onResultOf(toName())
+    .compound(Ordering.natural().onResultOf(toKey()));
   public static final Comparator<Plugin> NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER)
     .onResultOf(PluginToName.INSTANCE)
     .compound(
-      Ordering.from(CASE_INSENSITIVE_ORDER).onResultOf(PluginToKey.INSTANCE)
+      Ordering.from(CASE_INSENSITIVE_ORDER).onResultOf(PluginToKeyFunction.INSTANCE)
     );
   public static final Comparator<PluginUpdate> NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING)
     .onResultOf(PluginUpdateToPlugin.INSTANCE);
 
-  public void writePluginMetadata(JsonWriter jsonWriter, PluginInfo info) {
-    jsonWriter.beginObject();
+  void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category) {
+    json.beginObject();
 
-    writeMetadata(jsonWriter, info);
-
-    jsonWriter.endObject();
-  }
-
-  public void writeMetadata(JsonWriter jsonWriter, PluginInfo pluginMetadata) {
-    jsonWriter.prop(PROPERTY_KEY, pluginMetadata.getKey());
-    jsonWriter.prop(PROPERTY_NAME, pluginMetadata.getName());
-    jsonWriter.prop(PROPERTY_DESCRIPTION, pluginMetadata.getDescription());
-    Version version = pluginMetadata.getVersion();
+    json.prop(PROPERTY_KEY, pluginInfo.getKey());
+    json.prop(PROPERTY_NAME, pluginInfo.getName());
+    json.prop(PROPERTY_DESCRIPTION, pluginInfo.getDescription());
+    Version version = pluginInfo.getVersion();
     if (version != null) {
-      jsonWriter.prop(PROPERTY_VERSION, version.getName());
+      json.prop(PROPERTY_VERSION, version.getName());
     }
-    jsonWriter.prop(PROPERTY_LICENSE, pluginMetadata.getLicense());
-    jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, pluginMetadata.getOrganizationName());
-    jsonWriter.prop(PROPERTY_ORGANIZATION_URL, pluginMetadata.getOrganizationUrl());
-    jsonWriter.prop(PROPERTY_HOMEPAGE_URL, pluginMetadata.getHomepageUrl());
-    jsonWriter.prop(PROPERTY_ISSUE_TRACKER_URL, pluginMetadata.getIssueTrackerUrl());
-    jsonWriter.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginMetadata.getImplementationBuild());
+    json.prop(PROPERTY_CATEGORY, category);
+    json.prop(PROPERTY_LICENSE, pluginInfo.getLicense());
+    json.prop(PROPERTY_ORGANIZATION_NAME, pluginInfo.getOrganizationName());
+    json.prop(PROPERTY_ORGANIZATION_URL, pluginInfo.getOrganizationUrl());
+    json.prop(PROPERTY_HOMEPAGE_URL, pluginInfo.getHomepageUrl());
+    json.prop(PROPERTY_ISSUE_TRACKER_URL, pluginInfo.getIssueTrackerUrl());
+    json.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginInfo.getImplementationBuild());
+
+    json.endObject();
   }
 
-  public void writePluginUpdate(JsonWriter jsonWriter, PluginUpdate pluginUpdate) {
-    jsonWriter.beginObject();
-    Plugin plugin = pluginUpdate.getPlugin();
-
-    writeMetadata(jsonWriter, plugin);
-
-    writeRelease(jsonWriter, pluginUpdate.getRelease());
-
-    writeUpdate(jsonWriter, pluginUpdate);
-
-    jsonWriter.endObject();
+  public void writePluginInfoList(JsonWriter json, Iterable<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey, String propertyName) {
+    json.name(propertyName);
+    json.beginArray();
+    for (PluginInfo pluginInfo : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+      Plugin plugin = compatiblePluginsByKey.get(pluginInfo.getKey());
+      writePluginInfo(json, pluginInfo, categoryOrNull(plugin));
+    }
+    json.endArray();
   }
 
-  public void writeMetadata(JsonWriter jsonWriter, Plugin plugin) {
+  public void writePlugin(JsonWriter jsonWriter, Plugin plugin) {
     jsonWriter.prop(PROPERTY_KEY, plugin.getKey());
     jsonWriter.prop(PROPERTY_NAME, plugin.getName());
     jsonWriter.prop(PROPERTY_CATEGORY, plugin.getCategory());
@@ -121,6 +126,16 @@ public class PluginWSCommons {
     jsonWriter.prop(PROPERTY_ISSUE_TRACKER_URL, plugin.getIssueTrackerUrl());
   }
 
+  public void writePluginUpdate(JsonWriter json, PluginUpdate pluginUpdate) {
+    Plugin plugin = pluginUpdate.getPlugin();
+
+    json.beginObject();
+    writePlugin(json, plugin);
+    writeRelease(json, pluginUpdate.getRelease());
+    writeUpdate(json, pluginUpdate);
+    json.endObject();
+  }
+
   public void writeRelease(JsonWriter jsonWriter, Release release) {
     jsonWriter.name(OBJECT_RELEASE).beginObject();
 
@@ -214,61 +229,57 @@ public class PluginWSCommons {
    * "updateCenterRefresh": "2015-04-24T16:08:36+0200"
    * </pre>
    */
-  public void writeUpdateCenterProperties(JsonWriter jsonWriter, UpdateCenter updateCenter) {
-    jsonWriter.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.getDate());
-  }
-
-  private enum ReleaseToArtifact implements Function<Release, Artifact> {
-    INSTANCE;
-
-    @Override
-    public Artifact apply(@Nonnull Release input) {
-      return input.getArtifact();
+  public void writeUpdateCenterProperties(JsonWriter json, Optional<UpdateCenter> updateCenter) {
+    if (updateCenter.isPresent()) {
+      json.propDateTime(PROPERTY_UPDATE_CENTER_REFRESH, updateCenter.get().getDate());
     }
   }
 
-  private enum PluginMetadataToName implements Function<PluginInfo, String> {
+  enum PluginToKeyFunction implements Function<Plugin, String> {
     INSTANCE;
 
     @Override
-    public String apply(@Nonnull PluginInfo input) {
-      return input.getName();
+    public String apply(@Nonnull Plugin input) {
+      return input.getKey();
     }
   }
 
-  private enum PluginMetadataToKey implements Function<PluginInfo, String> {
+  private enum ReleaseToArtifact implements Function<Release, Artifact> {
     INSTANCE;
-
     @Override
-    public String apply(@Nonnull PluginInfo input) {
-      return input.getKey();
+    public Artifact apply(@Nonnull Release input) {
+      return input.getArtifact();
     }
+
   }
 
   private enum PluginUpdateToPlugin implements Function<PluginUpdate, Plugin> {
     INSTANCE;
-
     @Override
     public Plugin apply(@Nonnull PluginUpdate input) {
       return input.getPlugin();
     }
   }
 
-  private enum PluginToKey implements Function<Plugin, String> {
+  private enum PluginToName implements Function<Plugin, String> {
     INSTANCE;
-
     @Override
     public String apply(@Nonnull Plugin input) {
-      return input.getKey();
+      return input.getName();
     }
   }
 
-  private enum PluginToName implements Function<Plugin, String> {
-    INSTANCE;
+  static String categoryOrNull(Plugin plugin) {
+    return plugin != null ? plugin.getCategory() : null;
+  }
 
-    @Override
-    public String apply(@Nonnull Plugin input) {
-      return input.getName();
-    }
+  private static List<Plugin> compatiblePlugins(UpdateCenterMatrixFactory updateCenterMatrixFactory) {
+    Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(false);
+    return updateCenter.isPresent() ? updateCenter.get().findAllCompatiblePlugins() : Collections.<Plugin>emptyList();
+  }
+
+  static ImmutableMap<String, Plugin> compatiblePluginsByKey(UpdateCenterMatrixFactory updateCenterMatrixFactory) {
+    List<Plugin> compatiblePlugins = compatiblePlugins(updateCenterMatrixFactory);
+    return Maps.uniqueIndex(compatiblePlugins, PluginToKeyFunction.INSTANCE);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
index 1a8a3bc78e5..24473e9ed95 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import org.sonar.api.server.ws.Request;
@@ -32,6 +33,7 @@ import org.sonar.updatecenter.common.PluginUpdate;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.sonar.updatecenter.common.UpdateCenter;
 
 import static java.lang.String.format;
 
@@ -80,11 +82,17 @@ public class UpdateAction implements PluginsWsAction {
 
   @Nonnull
   private PluginUpdate findPluginUpdateByKey(String key) {
-    PluginUpdate pluginUpdate = Iterables.find(
-      updateCenterFactory.getUpdateCenter(false).findPluginUpdates(),
-      new PluginKeyPredicate(key),
-      MISSING_PLUGIN
+    Optional<UpdateCenter> updateCenter = updateCenterFactory.getUpdateCenter(false);
+    PluginUpdate pluginUpdate = MISSING_PLUGIN;
+
+    if (updateCenter.isPresent()) {
+      pluginUpdate = Iterables.find(
+        updateCenter.get().findPluginUpdates(),
+        new PluginKeyPredicate(key),
+        MISSING_PLUGIN
       );
+    }
+
     if (pluginUpdate == MISSING_PLUGIN) {
       throw new IllegalArgumentException(
         format("No plugin with key '%s' or plugin '%s' is already in latest compatible version", key, key));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
index 1e6014366bd..430d60cdfd9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
@@ -20,9 +20,13 @@
 package org.sonar.server.plugins.ws;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Ordering;
 import com.google.common.io.Resources;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nonnull;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -33,10 +37,6 @@ import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.PluginUpdate;
 import org.sonar.updatecenter.common.UpdateCenter;
 
-import javax.annotation.Nonnull;
-import java.util.Collection;
-import java.util.List;
-
 /**
  * Implementation of the {@code updates} action for the Plugins WebService.
  */
@@ -60,8 +60,8 @@ public class UpdatesAction implements PluginsWsAction {
   private final PluginUpdateAggregator aggregator;
 
   public UpdatesAction(UpdateCenterMatrixFactory updateCenterMatrixFactory,
-                       PluginWSCommons pluginWSCommons,
-                       PluginUpdateAggregator aggregator) {
+    PluginWSCommons pluginWSCommons,
+    PluginUpdateAggregator aggregator) {
     this.updateCenterMatrixFactory = updateCenterMatrixFactory;
     this.pluginWSCommons = pluginWSCommons;
     this.aggregator = aggregator;
@@ -87,7 +87,7 @@ public class UpdatesAction implements PluginsWsAction {
     JsonWriter jsonWriter = response.newJsonWriter();
     jsonWriter.beginObject();
 
-    UpdateCenter updateCenter = updateCenterMatrixFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
+    Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH);
 
     writePlugins(jsonWriter, updateCenter);
 
@@ -97,11 +97,13 @@ public class UpdatesAction implements PluginsWsAction {
     jsonWriter.close();
   }
 
-  private void writePlugins(JsonWriter jsonWriter, UpdateCenter updateCenter) {
+  private void writePlugins(JsonWriter jsonWriter, Optional<UpdateCenter> updateCenter) {
     jsonWriter.name(ARRAY_PLUGINS);
     jsonWriter.beginArray();
-    for (PluginUpdateAggregate aggregate : retrieveUpdatablePlugins(updateCenter)) {
-      writePluginUpdateAggregate(jsonWriter, aggregate);
+    if (updateCenter.isPresent()) {
+      for (PluginUpdateAggregate aggregate : retrieveUpdatablePlugins(updateCenter.get())) {
+        writePluginUpdateAggregate(jsonWriter, aggregate);
+      }
     }
     jsonWriter.endArray();
   }
@@ -110,7 +112,7 @@ public class UpdatesAction implements PluginsWsAction {
     jsonWriter.beginObject();
     Plugin plugin = aggregate.getPlugin();
 
-    pluginWSCommons.writeMetadata(jsonWriter, plugin);
+    pluginWSCommons.writePlugin(jsonWriter, plugin);
 
     writeUpdates(jsonWriter, aggregate.getUpdates());
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 404a93a8c16..cbc4aeee1a3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -162,7 +162,7 @@ public final class JRubyFacade {
   }
 
   public UpdateCenter getUpdatePluginCenter(boolean forceReload) {
-    return get(UpdateCenterMatrixFactory.class).getUpdateCenter(forceReload);
+    return get(UpdateCenterMatrixFactory.class).getUpdateCenter(forceReload).orNull();
   }
 
   public PluginReferential getInstalledPluginReferential() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
index 305fdab3cdd..0f9f96525c9 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.platform.ws;
 
+import com.google.common.base.Optional;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.server.ws.Request;
@@ -57,7 +58,7 @@ public class UpgradesActionTest {
 
   @Before
   public void wireMocks() {
-    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
     when(updateCenter.getDate()).thenReturn(DateUtils.parseDateTime("2015-04-24T16:08:36+0200"));
   }
 
@@ -87,6 +88,15 @@ public class UpgradesActionTest {
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_UPGRADE_LIST);
   }
 
+  @Test
+  public void empty_array_is_returned_when_update_center_is_unavailable() throws Exception {
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent());
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_UPGRADE_LIST);
+  }
+
   @Test
   public void verify_JSON_response_against_example() throws Exception {
     SonarUpdate sonarUpdate = createSonar_51_update();
@@ -126,7 +136,7 @@ public class UpgradesActionTest {
         .setDescription("New overall layout, merge Issues Drilldown [...]")
         .setDownloadUrl("http://dist.sonar.codehaus.org/sonarqube-5.1.zip")
         .setChangelogUrl("http://jira.sonarsource.com/secure/ReleaseNote.jspa?projectId=11694&version=20666")
-      );
+    );
 
     sonarUpdate.addIncompatiblePlugin(brandingPlugin);
     sonarUpdate.addPluginToUpgrade(new Release(viewsPlugin, Version.create("2.8")));
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
index 802e6f011b1..7f78fc6c538 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
@@ -19,6 +19,9 @@
  */
 package org.sonar.server.plugins;
 
+import com.google.common.base.Optional;
+import java.io.File;
+import java.net.URI;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -36,9 +39,6 @@ import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import java.io.File;
-import java.net.URI;
-
 import static com.google.common.collect.Lists.newArrayList;
 import static org.apache.commons.io.FileUtils.copyFileToDirectory;
 import static org.apache.commons.io.FileUtils.touch;
@@ -70,7 +70,7 @@ public class PluginDownloaderTest {
   public void before() throws Exception {
     updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class);
     updateCenter = mock(UpdateCenter.class);
-    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
 
     httpDownloader = mock(HttpDownloader.class);
     doAnswer(new Answer() {
@@ -124,6 +124,18 @@ public class PluginDownloaderTest {
     assertThat(new File(downloadDir, "test-1.0.jar.tmp")).doesNotExist();
   }
 
+  @Test
+  public void download_when_update_center_is_unavailable_with_no_exception_thrown() {
+    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent());
+
+    Plugin test = new Plugin("test");
+    Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/test-1.0.jar");
+    test.addRelease(test10);
+
+    pluginDownloader.start();
+    pluginDownloader.download("foo", create("1.0"));
+  }
+
   /**
    * SONAR-4685
    */
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
index 9d6581bb583..b6d1e32e869 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
@@ -19,6 +19,9 @@
  */
 package org.sonar.server.plugins;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.config.Settings;
@@ -27,11 +30,8 @@ import org.sonar.api.utils.UriReader;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
@@ -41,43 +41,47 @@ import static org.mockito.Mockito.when;
 
 public class UpdateCenterClientTest {
 
-  private static final String BASE_URL = "http://update.sonarsource.org";
-  private UpdateCenterClient client;
-  private UriReader reader;
+  static final String BASE_URL = "http://update.sonarsource.org";
+  UriReader reader;
+  Settings settings;
+
+  UpdateCenterClient underTest;
 
   @Before
   public void startServer() throws Exception {
     reader = mock(UriReader.class);
-    Settings settings = new Settings().setProperty(UpdateCenterClient.URL_PROPERTY, BASE_URL);
-    client = new UpdateCenterClient(reader, settings);
+    settings = new Settings()
+      .setProperty(UpdateCenterClient.URL_PROPERTY, BASE_URL)
+      .setProperty(UpdateCenterClient.ACTIVATION_PROPERTY, true);
+    underTest = new UpdateCenterClient(reader, settings);
   }
 
   @Test
   public void downloadUpdateCenter() throws URISyntaxException {
     when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("publicVersions=2.2,2.3");
-    UpdateCenter plugins = client.getUpdateCenter();
+    UpdateCenter plugins = underTest.getUpdateCenter().get();
     verify(reader, times(1)).readString(new URI(BASE_URL), StandardCharsets.UTF_8);
     assertThat(plugins.getSonar().getVersions()).containsOnly(Version.create("2.2"), Version.create("2.3"));
-    assertThat(client.getLastRefreshDate()).isNotNull();
+    assertThat(underTest.getLastRefreshDate()).isNotNull();
   }
 
   @Test
   public void not_available_before_initialization() {
-    assertThat(client.getLastRefreshDate()).isNull();
+    assertThat(underTest.getLastRefreshDate()).isNull();
   }
 
   @Test
   public void ignore_connection_errors() {
     when(reader.readString(any(URI.class), eq(StandardCharsets.UTF_8))).thenThrow(new SonarException());
-    assertThat(client.getUpdateCenter()).isNull();
+    assertThat(underTest.getUpdateCenter()).isAbsent();
   }
 
   @Test
   public void cache_data() throws Exception {
     when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("sonar.versions=2.2,2.3");
 
-    client.getUpdateCenter();
-    client.getUpdateCenter();
+    underTest.getUpdateCenter();
+    underTest.getUpdateCenter();
 
     verify(reader, times(1)).readString(new URI(BASE_URL), StandardCharsets.UTF_8);
   }
@@ -86,9 +90,16 @@ public class UpdateCenterClientTest {
   public void forceRefresh() throws Exception {
     when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("sonar.versions=2.2,2.3");
 
-    client.getUpdateCenter();
-    client.getUpdateCenter(true);
+    underTest.getUpdateCenter();
+    underTest.getUpdateCenter(true);
 
     verify(reader, times(2)).readString(new URI(BASE_URL), StandardCharsets.UTF_8);
   }
+
+  @Test
+  public void update_center_is_null_when_property_is_false() {
+    settings.setProperty(UpdateCenterClient.ACTIVATION_PROPERTY, false);
+
+    assertThat(underTest.getUpdateCenter()).isAbsent();
+  }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
index 8d28df8418a..6bb398cfadc 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import org.junit.Before;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.utils.DateUtils;
@@ -74,7 +75,7 @@ public abstract class AbstractUpdateCenterBasedPluginsWsActionTest {
 
   @Before
   public void wireMocksTogether() {
-    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
     when(updateCenter.getDate()).thenReturn(DateUtils.parseDateTime("2015-04-24T16:08:36+0200"));
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
index 205c657fa45..2524a4962ce 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.DateUtils;
@@ -26,9 +27,11 @@ import org.sonar.server.ws.WsTester;
 import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.PluginUpdate;
 import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
 
 import static com.google.common.collect.ImmutableList.of;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Mockito.when;
 import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE;
@@ -80,11 +83,20 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
   }
 
+  @Test
+  public void empty_array_is_returned_when_update_center_is_not_accessible() throws Exception {
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent());
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
+  }
+
   @Test
   public void verify_properties_displayed_in_json_per_plugin() throws Exception {
     when(updateCenter.findAvailablePlugins()).thenReturn(of(
       pluginUpdate(FULL_PROPERTIES_PLUGIN_RELEASE, COMPATIBLE)
-    ));
+      ));
 
     underTest.handle(request, response);
 
@@ -114,7 +126,7 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio
   private void checkStatusDisplayedInJson(PluginUpdate.Status status, String expectedValue) throws Exception {
     when(updateCenter.findAvailablePlugins()).thenReturn(of(
       pluginUpdate(release(PLUGIN_1, "1.0.0"), status)
-    ));
+      ));
 
     underTest.handle(request, response);
 
@@ -128,7 +140,7 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio
         "    }" +
         "  ]" +
         "}"
-    );
+      );
   }
 
   @Test
@@ -139,6 +151,6 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio
       pluginUpdate("key2", "name2"),
       pluginUpdate("key0", "name0"),
       pluginUpdate("key1", "name1")
-    ));
+      ));
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
index 6cac3de746d..fd4d2cd478f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import org.junit.Before;
 import org.junit.Rule;
@@ -49,7 +50,7 @@ public class InstallActionTest {
   private static final String ACTION_KEY = "install";
   private static final String KEY_PARAM = "key";
   private static final String PLUGIN_KEY = "pluginKey";
-  
+
   @Rule
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
 
@@ -68,7 +69,7 @@ public class InstallActionTest {
 
   @Before
   public void wireMocks() {
-    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
 
     userSessionRule.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
   }
@@ -123,6 +124,16 @@ public class InstallActionTest {
     validRequest.execute();
   }
 
+  @Test
+  public void IAE_is_raised_when_update_center_is_unavailable() throws Exception {
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent());
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("No plugin with key 'pluginKey'");
+
+    validRequest.execute();
+  }
+
   @Test
   public void if_plugin_is_found_available_download_is_triggered_with_latest_version_from_updatecenter() throws Exception {
     Version version = Version.create("1.0");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
index 2087259a96e..6e0ba04f45c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
@@ -19,7 +19,9 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import java.io.File;
+import java.util.Arrays;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -27,11 +29,15 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.ws.WsTester;
+import org.sonar.updatecenter.common.Plugin;
+import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
 import static com.google.common.collect.ImmutableList.of;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.test.JsonAssert.assertJson;
@@ -46,11 +52,13 @@ public class InstalledActionTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
-  private InstalledAction underTest = new InstalledAction(pluginRepository, new PluginWSCommons());
+  ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+  UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
 
-  private Request request = mock(Request.class);
-  private WsTester.TestResponse response = new WsTester.TestResponse();
+  InstalledAction underTest = new InstalledAction(pluginRepository, new PluginWSCommons(), updateCenterMatrixFactory);
+
+  Request request = mock(Request.class);
+  WsTester.TestResponse response = new WsTester.TestResponse();
 
   @Test
   public void action_installed_is_defined() {
@@ -76,6 +84,15 @@ public class InstalledActionTest {
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
   }
 
+  @Test
+  public void empty_array_when_update_center_is_unavailable() throws Exception {
+    when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.<UpdateCenter>absent());
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
+  }
+
   @Test
   public void empty_fields_are_not_serialized_to_json() throws Exception {
     when(pluginRepository.getPluginInfos()).thenReturn(
@@ -106,6 +123,14 @@ public class InstalledActionTest {
         .setJarFile(new File(getClass().getResource(jarFilename).toURI()))
       )
       );
+    UpdateCenter updateCenter = mock(UpdateCenter.class);
+    when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
+    when(updateCenter.findAllCompatiblePlugins()).thenReturn(
+      Arrays.asList(
+        new Plugin("plugKey")
+          .setCategory("cat_1")
+        )
+      );
 
     underTest.handle(request, response);
 
@@ -118,6 +143,7 @@ public class InstalledActionTest {
         "      \"name\": \"plugName\"," +
         "      \"description\": \"desc_it\"," +
         "      \"version\": \"1.0\"," +
+        "      \"category\":\"cat_1\"," +
         "      \"license\": \"license_hey\"," +
         "      \"organizationName\": \"org_name\"," +
         "      \"organizationUrl\": \"org_url\"," +
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
index 143747095e8..b35c4c31c1a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
@@ -19,17 +19,23 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
+import java.util.Arrays;
 import org.junit.Test;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.plugins.PluginDownloader;
 import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.ws.WsTester;
+import org.sonar.updatecenter.common.Plugin;
+import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
 import static com.google.common.collect.ImmutableList.of;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.test.JsonAssert.assertJson;
@@ -40,7 +46,8 @@ public class PendingActionTest {
 
   PluginDownloader pluginDownloader = mock(PluginDownloader.class);
   ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
-  PendingAction underTest = new PendingAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());
+  UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
+  PendingAction underTest = new PendingAction(pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory);
   Request request = mock(Request.class);
   WsTester.TestResponse response = new WsTester.TestResponse();
 
@@ -73,9 +80,31 @@ public class PendingActionTest {
       );
   }
 
+  @Test
+  public void empty_arrays_are_returned_when_update_center_is_unavailable() throws Exception {
+    when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.<UpdateCenter>absent());
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(
+      "{" +
+        "  \"installing\": []," +
+        "  \"removing\": []" +
+        "}"
+      );
+  }
+
   @Test
   public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception {
     when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(gitPluginInfo()));
+    UpdateCenter updateCenter = mock(UpdateCenter.class);
+    when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
+    when(updateCenter.findAllCompatiblePlugins()).thenReturn(
+      Arrays.asList(
+        new Plugin("scmgit")
+          .setCategory("cat_1")
+        )
+      );
 
     underTest.handle(request, response);
 
@@ -89,6 +118,7 @@ public class PendingActionTest {
         "      \"description\": \"Git SCM Provider.\"," +
         "      \"version\": \"1.0\"," +
         "      \"license\": \"GNU LGPL 3\"," +
+        "      \"category\":\"cat_1\"," +
         "      \"organizationName\": \"SonarSource\"," +
         "      \"organizationUrl\": \"http://www.sonarsource.com\"," +
         "      \"homepageUrl\": \"http://redirect.sonarsource.com/plugins/scmgit.html\"," +
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
index 11403c59936..146115b6a4b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
@@ -45,7 +45,7 @@ public class PluginWSCommonsTest {
 
   @Test
   public void verify_properties_written_by_writePluginMetadata() {
-    underTest.writePluginMetadata(jsonWriter, gitPluginInfo());
+    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null);
 
     jsonWriter.close();
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo("{" +
@@ -63,9 +63,7 @@ public class PluginWSCommonsTest {
 
   @Test
   public void verify_properties_written_by_writeMetadata() {
-    jsonWriter.beginObject();
-    underTest.writeMetadata(jsonWriter, gitPluginInfo());
-    jsonWriter.endObject();
+    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1");
 
     jsonWriter.close();
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo("{" +
@@ -74,6 +72,7 @@ public class PluginWSCommonsTest {
       "  \"description\": \"Git SCM Provider.\"," +
       "  \"version\": \"1.0\"," +
       "  \"license\": \"GNU LGPL 3\"," +
+      "  \"category\":\"cat_1\"" +
       "  \"organizationName\": \"SonarSource\"," +
       "  \"organizationUrl\": \"http://www.sonarsource.com\"," +
       "  \"homepageUrl\": \"http://redirect.sonarsource.com/plugins/scmgit.html\"," +
@@ -105,7 +104,7 @@ public class PluginWSCommonsTest {
   @Test
   public void verify_properties_written_by_writeMetadata_from_plugin() {
     jsonWriter.beginObject();
-    underTest.writeMetadata(jsonWriter, newPlugin());
+    underTest.writePlugin(jsonWriter, newPlugin());
     jsonWriter.endObject();
 
     jsonWriter.close();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
index 22c8ac0fc05..4f840c72b49 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.plugins.ws;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import org.junit.Before;
 import org.junit.Rule;
@@ -70,7 +71,7 @@ public class UpdateActionTest {
 
   @Before
   public void setUp() {
-    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
 
     userSessionRule.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
   }
@@ -125,12 +126,22 @@ public class UpdateActionTest {
     underTest.handle(validRequest, response);
   }
 
+  @Test
+  public void IAE_is_raised_when_update_center_is_unavailable() throws Exception {
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.<UpdateCenter>absent());
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("No plugin with key 'pluginKey'");
+
+    underTest.handle(validRequest, response);
+  }
+
   @Test
   public void if_plugin_has_an_update_download_is_triggered_with_latest_version_from_updatecenter() throws Exception {
     Version version = Version.create("1.0");
     when(updateCenter.findPluginUpdates()).thenReturn(ImmutableList.of(
       PluginUpdate.createWithStatus(new Release(new Plugin(PLUGIN_KEY), version), Status.COMPATIBLE)
-      ));
+    ));
 
     underTest.handle(validRequest, response);
 
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfoFunctions.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfoFunctions.java
new file mode 100644
index 00000000000..c0aaea96e83
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfoFunctions.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.core.platform;
+
+import com.google.common.base.Function;
+import javax.annotation.Nonnull;
+
+public final class PluginInfoFunctions {
+
+  private PluginInfoFunctions() {
+    // utility class
+  }
+
+  public static Function<PluginInfo, String> toName() {
+    return PluginInfoToName.INSTANCE;
+  }
+
+  public static Function<PluginInfo, String> toKey() {
+    return PluginInfoToKey.INSTANCE;
+  }
+
+  private enum PluginInfoToName implements Function<PluginInfo, String> {
+    INSTANCE;
+
+    @Override
+    public String apply(@Nonnull PluginInfo input) {
+      return input.getName();
+    }
+  }
+
+  private enum PluginInfoToKey implements Function<PluginInfo, String> {
+    INSTANCE;
+
+    @Override
+    public String apply(@Nonnull PluginInfo input) {
+      return input.getKey();
+    }
+  }
+}
-- 
cgit v1.2.3