]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10034 drop support of reflection-based Pico Container lifecycle management
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 20 May 2019 10:17:54 +0000 (12:17 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 23 May 2019 18:21:09 +0000 (20:21 +0200)
one must now implement org.picocontainer.Startable, org.sonar.api.Startable, java.io.Closeable and/or java.lang.Closeable

17 files changed:
server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java
server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java
server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java
server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java
server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java
sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java [deleted file]
sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java

index 52b78b22a065c2c7140585cfa8c73a022fe7f0a7..9dc7160622fcd8877b278270ed22cfe3f45ae68a 100644 (file)
@@ -21,18 +21,17 @@ package org.sonar.ce.task.container;
 
 import java.util.List;
 import org.picocontainer.ComponentAdapter;
-import org.picocontainer.ComponentMonitor;
 import org.picocontainer.DefaultPicoContainer;
+import org.picocontainer.LifecycleStrategy;
 import org.picocontainer.MutablePicoContainer;
 import org.picocontainer.behaviors.OptInCaching;
-import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
 import org.picocontainer.monitors.NullComponentMonitor;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.ComponentContainer;
 import org.sonar.core.platform.ContainerPopulator;
 import org.sonar.core.platform.Module;
-import org.sonar.core.platform.StopSafeReflectionLifecycleStrategy;
+import org.sonar.core.platform.StartableCloseableSafeLifecyleStrategy;
 
 import static java.util.Objects.requireNonNull;
 
@@ -61,15 +60,14 @@ public class TaskContainerImpl extends ComponentContainer implements TaskContain
    * and lazily starts its components.
    */
   private static MutablePicoContainer createContainer(ComponentContainer parent) {
-    ComponentMonitor componentMonitor = new NullComponentMonitor();
-    ReflectionLifecycleStrategy lifecycleStrategy = new StopSafeReflectionLifecycleStrategy(componentMonitor) {
+    LifecycleStrategy lifecycleStrategy = new StartableCloseableSafeLifecyleStrategy() {
       @Override
       public boolean isLazy(ComponentAdapter<?> adapter) {
         return adapter.getComponentImplementation().getAnnotation(EagerStart.class) == null;
       }
     };
 
-    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, parent.getPicoContainer(), componentMonitor);
+    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, parent.getPicoContainer(), new NullComponentMonitor());
   }
 
   @Override
index fc368c72668b261de7672eafb5c79665fe252006..faa06886406f17c8611081f0c4adec80d5e22c62 100644 (file)
@@ -29,6 +29,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Properties;
+import org.picocontainer.Startable;
 import org.slf4j.LoggerFactory;
 import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 
@@ -46,7 +47,7 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
  * This HTTP server exports data required for display of System Info page (and the related web service).
  * It listens on loopback address only, so it does not need to be secure (no HTTPS, no authentication).
  */
-public class CeHttpServer {
+public class CeHttpServer implements Startable {
 
   private final Properties processProps;
   private final List<HttpAction> actions;
@@ -60,7 +61,7 @@ public class CeHttpServer {
     this.nanoHttpd = new CeNanoHttpd(InetAddress.getLoopbackAddress().getHostAddress(), 0, actionRegistry);
   }
 
-  // do not rename. This naming convention is required for picocontainer.
+  @Override
   public void start() {
     try {
       registerActions();
@@ -85,7 +86,7 @@ public class CeHttpServer {
     }
   }
 
-  // do not rename. This naming convention is required for picocontainer.
+  @Override
   public void stop() {
     nanoHttpd.stop();
   }
index bcbb9ce67ddace6de522e51a7f7e00f311dc1106..bc09347827fd0927fd6133c03f5107b3443476d8 100644 (file)
 package org.sonar.server.platform.db.migration.engine;
 
 import org.picocontainer.ComponentAdapter;
-import org.picocontainer.ComponentMonitor;
 import org.picocontainer.DefaultPicoContainer;
+import org.picocontainer.LifecycleStrategy;
 import org.picocontainer.MutablePicoContainer;
 import org.picocontainer.behaviors.OptInCaching;
-import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
 import org.picocontainer.monitors.NullComponentMonitor;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.core.platform.ComponentContainer;
-import org.sonar.core.platform.StopSafeReflectionLifecycleStrategy;
+import org.sonar.core.platform.StartableCloseableSafeLifecyleStrategy;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,14 +48,13 @@ public class MigrationContainerImpl extends ComponentContainer implements Migrat
    * Creates a PicContainer which extends the specified ComponentContainer <strong>but is not referenced in return</strong>.
    */
   private static MutablePicoContainer createContainer(ComponentContainer parent) {
-    ComponentMonitor componentMonitor = new NullComponentMonitor();
-    ReflectionLifecycleStrategy lifecycleStrategy = new StopSafeReflectionLifecycleStrategy(componentMonitor) {
+    LifecycleStrategy lifecycleStrategy = new StartableCloseableSafeLifecyleStrategy() {
       @Override
       public boolean isLazy(ComponentAdapter<?> adapter) {
         return true;
       }
     };
-    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, parent.getPicoContainer(), componentMonitor);
+    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, parent.getPicoContainer(), new NullComponentMonitor());
   }
 
   @Override
index eee2074629066478f01073a5f698d5affe2e4c56..fc1315a2d266f55cc603104f35c9370de20ce0be 100644 (file)
 package org.sonar.server.util;
 
 import java.util.concurrent.ExecutorService;
+import org.picocontainer.Startable;
 
 /**
  * ExecutorService that exposes a {@code stop} method which can be invoked by Pico container to shutdown properly
  * the service.
  */
-public interface StoppableExecutorService extends ExecutorService {
+public interface StoppableExecutorService extends ExecutorService, Startable {
+  @Override
+  default void start() {
+    // nothing to do
+  }
 
-  /**
-   * Stops the ExecutorService nicely (ie. first let a little time for jobs to end and then abort them)
-   */
-  void stop();
 }
index a9fe6290c08f8ea0a990458c1d847026b846d274..88b1692362e99c66245a52910e32253f11b7903b 100644 (file)
 package org.sonar.server.platform.web;
 
 import java.util.Arrays;
-import javax.servlet.ServletException;
+import org.picocontainer.Startable;
 import org.sonar.api.web.ServletFilter;
 
 /**
  * @since 3.5
  */
-public class RegisterServletFilters {
+public class RegisterServletFilters implements Startable {
   private final ServletFilter[] filters;
 
   public RegisterServletFilters(ServletFilter[] filters) {
@@ -37,7 +37,8 @@ public class RegisterServletFilters {
     this(new ServletFilter[0]);
   }
 
-  public void start() throws ServletException {
+  @Override
+  public void start() {
     if (MasterServletFilter.INSTANCE != null) {
       // Probably a database upgrade. MasterSlaveFilter was instantiated by the servlet container
       // while picocontainer was not completely up.
@@ -45,4 +46,9 @@ public class RegisterServletFilters {
       MasterServletFilter.INSTANCE.initFilters(Arrays.asList(filters));
     }
   }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
 }
index 8073e5a4c25e1d4c1af3cb1021734774252f7415..4b9df426660ec4eedac0edbd4e1a8c3a91947075 100644 (file)
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
+import org.picocontainer.Startable;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
@@ -47,7 +48,7 @@ import static org.sonar.server.qualityprofile.ActiveRuleInheritance.NONE;
  * Synchronize Quality profiles during server startup
  */
 @ServerSide
-public class RegisterQualityProfiles {
+public class RegisterQualityProfiles implements Startable {
 
   private static final Logger LOGGER = Loggers.get(RegisterQualityProfiles.class);
 
@@ -69,6 +70,7 @@ public class RegisterQualityProfiles {
     this.system2 = system2;
   }
 
+  @Override
   public void start() {
     List<BuiltInQProfile> builtInQProfiles = builtInQProfileRepository.get();
     if (builtInQProfiles.isEmpty()) {
@@ -106,6 +108,11 @@ public class RegisterQualityProfiles {
     profiler.stopDebug();
   }
 
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
   private Map<QProfileName, RulesProfileDto> loadPersistedProfiles(DbSession dbSession) {
     return dbClient.qualityProfileDao().selectBuiltInRuleProfiles(dbSession).stream()
       .collect(MoreCollectors.uniqueIndex(rp -> new QProfileName(rp.getLanguage(), rp.getName())));
index 2279694159be13e75dd338a6c1aba767fb3be4a7..92a1f6e082957f8844810a3d4ffd34879be13977 100644 (file)
@@ -43,6 +43,7 @@ public class ClusterConfigurationCheck implements Startable {
     }
   }
 
+  @Override
   public void stop() {
     // Nothing to do
   }
index f68c44aea501c9a7cb0f82f67d4fcf0f24435af3..39bdbf8edfbf6490def74722894ef6d8c6ea96fe 100644 (file)
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nonnull;
+import org.picocontainer.Startable;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metrics;
@@ -40,7 +41,7 @@ import static com.google.common.collect.FluentIterable.from;
 import static com.google.common.collect.Iterables.concat;
 import static com.google.common.collect.Lists.newArrayList;
 
-public class RegisterMetrics {
+public class RegisterMetrics implements Startable {
 
   private static final Logger LOG = Loggers.get(RegisterMetrics.class);
 
@@ -59,10 +60,16 @@ public class RegisterMetrics {
     this(dbClient, new Metrics[] {});
   }
 
+  @Override
   public void start() {
     register(concat(CoreMetrics.getMetrics(), getPluginMetrics()));
   }
 
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
   void register(Iterable<Metric> metrics) {
     Profiler profiler = Profiler.create(LOG).startInfo("Register metrics");
     try (DbSession session = dbClient.openSession(false)) {
index e16a4cca99e5015229b1c69fc060eec767ce601d..fd2ff4590cf44b748e8442425cc8287585678693 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.startup;
 
+import org.picocontainer.Startable;
 import org.sonar.api.security.DefaultGroups;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
@@ -37,7 +38,7 @@ import java.util.Optional;
 
 import static java.lang.String.format;
 
-public class RegisterPermissionTemplates {
+public class RegisterPermissionTemplates implements Startable {
 
   private static final Logger LOG = Loggers.get(RegisterPermissionTemplates.class);
   private static final String DEFAULT_TEMPLATE_UUID = "default_template";
@@ -50,6 +51,7 @@ public class RegisterPermissionTemplates {
     this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
+  @Override
   public void start() {
     Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Register permission templates");
 
@@ -66,6 +68,11 @@ public class RegisterPermissionTemplates {
     profiler.stopDebug();
   }
 
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
   private PermissionTemplateDto getOrInsertDefaultTemplate(DbSession dbSession, String defaultOrganizationUuid) {
     PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().selectByUuid(dbSession, DEFAULT_TEMPLATE_UUID);
     if (permissionTemplateDto != null) {
index a05840d074108adfac06fa4a862d8521168e3e98..7be27df5c8a3af181a6b6268af2b4bc887639c67 100644 (file)
 package org.sonar.server.startup;
 
 import com.google.common.base.Strings;
-import org.sonar.api.utils.log.Loggers;
+import org.picocontainer.Startable;
 import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.utils.log.Loggers;
 import org.sonar.db.property.PropertiesDao;
 
 /**
  * @since 3.4
  */
-public class RenameDeprecatedPropertyKeys {
+public class RenameDeprecatedPropertyKeys implements Startable {
 
   private PropertiesDao dao;
   private PropertyDefinitions definitions;
@@ -38,6 +39,7 @@ public class RenameDeprecatedPropertyKeys {
     this.definitions = definitions;
   }
 
+  @Override
   public void start() {
     Loggers.get(RenameDeprecatedPropertyKeys.class).info("Rename deprecated property keys");
     for (PropertyDefinition definition : definitions.getAll()) {
@@ -46,4 +48,9 @@ public class RenameDeprecatedPropertyKeys {
       }
     }
   }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
 }
index 6e57afd8ead303c03fc63773c31fa6313c50602c..da2587ca9dfb46dd813d455413b3dd5c1d2dc473 100644 (file)
@@ -310,8 +310,7 @@ public class ComponentContainer implements ContainerPopulator.Container {
   }
 
   public static MutablePicoContainer createPicoContainer() {
-    NullComponentMonitor componentMonitor = new NullComponentMonitor();
-    return new ExtendedDefaultPicoContainer(new OptInCaching(), new StopSafeReflectionLifecycleStrategy(componentMonitor), null, componentMonitor);
+    return new ExtendedDefaultPicoContainer(new OptInCaching(), new StartableCloseableSafeLifecyleStrategy(), null, new NullComponentMonitor());
   }
 
   public ComponentContainer getParent() {
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java b/sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java
new file mode 100644 (file)
index 0000000..a2b912b
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 java.io.Closeable;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.picocontainer.ComponentAdapter;
+import org.picocontainer.LifecycleStrategy;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class StartableCloseableSafeLifecyleStrategy implements LifecycleStrategy, Serializable {
+  private static final Class<?>[] TYPES_WITH_LIFECYCLE = new Class[] {Startable.class, org.sonar.api.Startable.class, Closeable.class, AutoCloseable.class};
+
+  private static final Logger LOG = Loggers.get(StartableCloseableSafeLifecyleStrategy.class);
+
+  @Override
+  public void start(Object component) {
+    if (component instanceof Startable) {
+      ((Startable) component).start();
+    } else if (component instanceof org.sonar.api.Startable) {
+      ((org.sonar.api.Startable) component).start();
+    }
+  }
+
+  @Override
+  public void stop(Object component) {
+    try {
+      if (component instanceof Startable) {
+        ((Startable) component).stop();
+      } else if (component instanceof org.sonar.api.Startable) {
+        ((org.sonar.api.Startable) component).stop();
+      }
+    } catch (RuntimeException | Error e) {
+      Loggers.get(StartableCloseableSafeLifecyleStrategy.class)
+        .warn("Stopping of component {} failed", component.getClass().getCanonicalName(), e);
+    }
+  }
+
+  @Override
+  public void dispose(Object component) {
+    try {
+      if (component instanceof Closeable) {
+        ((Closeable) component).close();
+      } else if (component instanceof AutoCloseable) {
+        ((AutoCloseable) component).close();
+      }
+    } catch (Exception e) {
+      Loggers.get(StartableCloseableSafeLifecyleStrategy.class)
+        .warn("Dispose of component {} failed", component.getClass().getCanonicalName(), e);
+    }
+  }
+
+  @Override
+  public boolean hasLifecycle(Class<?> type) {
+    if (Arrays.stream(TYPES_WITH_LIFECYCLE).anyMatch(t1 -> t1.isAssignableFrom(type))) {
+      return true;
+    }
+
+    if (Stream.of("start", "stop").anyMatch(t -> hasMethod(type, t))) {
+      LOG.warn("Component of type {} defines methods start() and/or stop(). Neither will be invoked to start/stop the component." +
+        " Please implement either {} or {}",
+        type, Startable.class.getName(), org.sonar.api.Startable.class.getName());
+    }
+    if (hasMethod(type, "close")) {
+      LOG.warn("Component of type {} defines method close(). It won't be invoked to dispose the component." +
+        " Please implement either {} or {}",
+        type, Closeable.class.getName(), AutoCloseable.class.getName());
+    }
+    return false;
+  }
+
+  private static boolean hasMethod(Class<?> type, String methodName) {
+    try {
+      return type.getMethod(methodName) != null;
+    } catch (NoSuchMethodException e) {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean isLazy(ComponentAdapter<?> adapter) {
+    return false;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java b/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java
deleted file mode 100644 (file)
index 92c28c8..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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 org.picocontainer.ComponentMonitor;
-import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
-import org.sonar.api.utils.log.Loggers;
-
-/**
- * A {@link ReflectionLifecycleStrategy} which:
- * <li>
- *   <ul>implements support for methods {@code start()}, {@code stop()} and {@code close()} as methods of startable,
- *       stoppable and/or disposable components in a SonarQube container (whichever side the container is on)</ul>
- *   <ul>ensures that all stoppable and disposable components in a given container are stopped and/or disposed of
- *       even if a {@link RuntimeException} or a {@link Error} is thrown by one or more of those stoppable and/or
- *       disposable components</ul>
- * </li>
- */
-public class StopSafeReflectionLifecycleStrategy extends ReflectionLifecycleStrategy {
-  public StopSafeReflectionLifecycleStrategy(ComponentMonitor componentMonitor) {
-    super(componentMonitor, "start", "stop", "close");
-  }
-
-  @Override
-  public void stop(Object component) {
-    try {
-      super.stop(component);
-    } catch (RuntimeException | Error e) {
-      Loggers.get(StopSafeReflectionLifecycleStrategy.class)
-        .warn("Stopping of component {} failed", component.getClass().getCanonicalName(), e);
-    }
-  }
-
-  @Override
-  public void dispose(Object component) {
-    try {
-      super.dispose(component);
-    } catch (RuntimeException | Error e) {
-      Loggers.get(StopSafeReflectionLifecycleStrategy.class)
-        .warn("Dispose of component {} failed", component.getClass().getCanonicalName(), e);
-    }
-  }
-}
index 67bd7b63ff79f320f53cca53d94dd5059d505e9f..4584ce8991bb33364b4a5236c9c9ca26fb846a13 100644 (file)
  */
 package org.sonar.core.platform;
 
+import java.io.Closeable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.picocontainer.Startable;
 import org.picocontainer.injectors.ProviderAdapter;
 import org.sonar.api.Property;
 import org.sonar.api.config.PropertyDefinitions;
@@ -48,7 +50,7 @@ public class ComponentContainerTest {
   }
 
   @Test
-  public void should_start_and_stop() {
+  public void should_start_and_stop_component_extending_pico_Startable() {
     ComponentContainer container = spy(new ComponentContainer());
     container.addSingleton(StartableStoppableComponent.class);
     container.startComponents();
@@ -62,6 +64,37 @@ public class ComponentContainerTest {
     assertThat(container.getComponentByType(StartableStoppableComponent.class).stopped).isTrue();
   }
 
+  @Test
+  public void should_start_and_stop_component_extending_API_Startable() {
+    ComponentContainer container = spy(new ComponentContainer());
+    container.addSingleton(StartableStoppableApiComponent.class);
+    container.startComponents();
+
+    assertThat(container.getComponentByType(StartableStoppableApiComponent.class).started).isTrue();
+    assertThat(container.getComponentByType(StartableStoppableApiComponent.class).stopped).isFalse();
+    verify(container).doBeforeStart();
+    verify(container).doAfterStart();
+
+    container.stopComponents();
+    assertThat(container.getComponentByType(StartableStoppableApiComponent.class).stopped).isTrue();
+  }
+
+  @Test
+  public void should_not_start_and_stop_component_just_having_start_and_stop_method() {
+    ComponentContainer container = spy(new ComponentContainer());
+    container.addSingleton(ReflectionStartableStoppableComponent.class);
+    container.startComponents();
+
+    assertThat(container.getComponentByType(ReflectionStartableStoppableComponent.class).started).isFalse();
+    assertThat(container.getComponentByType(ReflectionStartableStoppableComponent.class).stopped).isFalse();
+    verify(container).doBeforeStart();
+    verify(container).doAfterStart();
+
+    container.stopComponents();
+    assertThat(container.getComponentByType(ReflectionStartableStoppableComponent.class).started).isFalse();
+    assertThat(container.getComponentByType(ReflectionStartableStoppableComponent.class).stopped).isFalse();
+  }
+
   @Test
   public void should_start_and_stop_hierarchy_of_containers() {
     StartableStoppableComponent parentComponent = new StartableStoppableComponent();
@@ -296,6 +329,10 @@ public class ComponentContainerTest {
       container.getComponentByType(FailingStopWithOOMComponent2.class)
     };
 
+    container.stopComponents();
+
+    assertThat(container.getPicoContainer().getLifecycleState().isDisposed()).isTrue();
+    Arrays.stream(components).forEach(cpt -> assertThat(cpt.stopped).isTrue());
   }
 
   @Test
@@ -308,6 +345,16 @@ public class ComponentContainerTest {
     container.execute();
   }
 
+  @Test
+  public void stop_exceptionin_API_component_should_not_hide_start_exception() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(UnstartableApiComponent.class, FailingStopWithISEComponent.class);
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to start");
+    container.execute();
+  }
+
   @Test
   public void should_execute_components() {
     ComponentContainer container = new ComponentContainer();
@@ -339,7 +386,7 @@ public class ComponentContainerTest {
    * Method close() must be executed after stop()
    */
   @Test
-  public void should_close_components_with_lifecycle() {
+  public void should_close_Closeable_components_with_lifecycle() {
     ComponentContainer container = new ComponentContainer();
     StartableCloseableComponent component = new StartableCloseableComponent();
     container.add(component);
@@ -351,24 +398,84 @@ public class ComponentContainerTest {
     assertThat(component.isClosedAfterStop).isTrue();
   }
 
-  public static class StartableStoppableComponent {
+  /**
+   * Method close() must be executed after stop()
+   */
+  @Test
+  public void should_close_AutoCloseable_components_with_lifecycle() {
+    ComponentContainer container = new ComponentContainer();
+    StartableAutoCloseableComponent component = new StartableAutoCloseableComponent();
+    container.add(component);
+
+    container.execute();
+
+    assertThat(component.isStopped).isTrue();
+    assertThat(component.isClosed).isTrue();
+    assertThat(component.isClosedAfterStop).isTrue();
+  }
+
+  public static class StartableStoppableComponent implements Startable {
     public boolean started = false;
     public boolean stopped = false;
 
+    @Override
     public void start() {
       started = true;
     }
 
+    @Override
     public void stop() {
       stopped = true;
     }
   }
 
-  public static class UnstartableComponent {
+  public static class StartableStoppableApiComponent implements org.sonar.api.Startable {
+    public boolean started = false;
+    public boolean stopped = false;
+
+    @Override
+    public void start() {
+      started = true;
+    }
+
+    @Override
+    public void stop() {
+      stopped = true;
+    }
+  }
+
+  public static class ReflectionStartableStoppableComponent {
+    public boolean started = false;
+    public boolean stopped = false;
+
+    public void start() {
+      started = true;
+    }
+
+    public void stop() {
+      stopped = true;
+    }
+  }
+
+  public static class UnstartableComponent implements Startable {
+    @Override
+    public void start() {
+      throw new IllegalStateException("Fail to start");
+    }
+
+    @Override
+    public void stop() {
+
+    }
+  }
+
+  public static class UnstartableApiComponent implements org.sonar.api.Startable {
+    @Override
     public void start() {
       throw new IllegalStateException("Fail to start");
     }
 
+    @Override
     public void stop() {
 
     }
@@ -431,22 +538,50 @@ public class ComponentContainerTest {
     public boolean isClosed = false;
 
     @Override
-    public void close() throws Exception {
+    public void close() {
       isClosed = true;
     }
   }
 
-  public static class StartableCloseableComponent implements AutoCloseable {
+  public static class StartableAutoCloseableComponent implements Startable,AutoCloseable {
     public boolean isClosed = false;
     public boolean isStopped = false;
     public boolean isClosedAfterStop = false;
 
+    @Override
+    public void start() {
+      // nothing to do
+    }
+
+    @Override
+    public void stop() {
+      isStopped = true;
+    }
+
+    @Override
+    public void close() {
+      isClosed = true;
+      isClosedAfterStop = isStopped;
+    }
+  }
+
+  public static class StartableCloseableComponent implements Startable, Closeable {
+    public boolean isClosed = false;
+    public boolean isStopped = false;
+    public boolean isClosedAfterStop = false;
+
+    @Override
+    public void start() {
+      // nothing to do
+    }
+
+    @Override
     public void stop() {
       isStopped = true;
     }
 
     @Override
-    public void close() throws Exception {
+    public void close() {
       isClosed = true;
       isClosedAfterStop = isStopped;
     }
index 4cc7746b72d6cf9d29273940990fe8afc8e5d2c8..0e168695980499bb0a3df7863b5b3d5bbf39b11c 100644 (file)
  */
 package org.sonar.core.platform;
 
-import java.io.IOException;
+import java.lang.reflect.Method;
 import org.junit.Test;
 import org.picocontainer.Characteristics;
 import org.picocontainer.MutablePicoContainer;
 import org.picocontainer.PicoLifecycleException;
+import org.picocontainer.Startable;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -31,11 +32,19 @@ import static org.junit.Assert.fail;
 public class PicoUtilsTest {
 
   @Test
-  public void shouldSanitizePicoLifecycleException() {
-    Throwable th = PicoUtils.sanitize(newPicoLifecycleException(false));
+  public void shouldSanitizePicoLifecycleException() throws NoSuchMethodException {
+    UncheckedFailureComponent instance = new UncheckedFailureComponent();
+    Method method = UncheckedFailureComponent.class.getMethod("start");
+    try {
+      instance.start();
+      fail("Start should have thrown a IllegalStateException");
+    }
+    catch (IllegalStateException e) {
+      Throwable th = PicoUtils.sanitize(new PicoLifecycleException(method, instance, e));
 
-    assertThat(th).isInstanceOf(IllegalStateException.class);
-    assertThat(th.getMessage()).isEqualTo("A good reason to fail");
+      assertThat(th).isInstanceOf(IllegalStateException.class);
+      assertThat(th.getMessage()).isEqualTo("A good reason to fail");
+    }
   }
 
   @Test
@@ -57,49 +66,35 @@ public class PicoUtilsTest {
   @Test
   public void shouldPropagateInitialUncheckedException() {
     try {
-      PicoUtils.propagate(newPicoLifecycleException(false));
+      PicoUtils.propagate(newPicoLifecycleException());
       fail();
     } catch (RuntimeException e) {
       assertThat(e).isInstanceOf(IllegalStateException.class);
     }
   }
 
-  @Test
-  public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() {
-    try {
-      PicoUtils.propagate(newPicoLifecycleException(true));
-      fail();
-    } catch (RuntimeException e) {
-      assertThat(e.getCause()).isInstanceOf(IOException.class);
-      assertThat(e.getCause().getMessage()).isEqualTo("Checked");
-    }
-  }
 
-  private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) {
+  private PicoLifecycleException newPicoLifecycleException() {
     MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE);
-    if (initialCheckedException) {
-      container.addComponent(CheckedFailureComponent.class);
-    } else {
-      container.addComponent(UncheckedFailureComponent.class);
-    }
+    container.addComponent(UncheckedFailureComponent.class);
     try {
       container.start();
-      return null;
+      throw new IllegalStateException("An exception should have been thrown by start()");
 
     } catch (PicoLifecycleException e) {
       return e;
     }
   }
 
-  public static class UncheckedFailureComponent {
+  public static class UncheckedFailureComponent implements Startable {
     public void start() {
       throw new IllegalStateException("A good reason to fail");
     }
-  }
 
-  public static class CheckedFailureComponent {
-    public void start() throws IOException {
-      throw new IOException("Checked");
+    @Override
+    public void stop() {
+      // nothing to do
     }
   }
+
 }
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java b/sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java
new file mode 100644 (file)
index 0000000..4c92df1
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 java.io.Closeable;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.Startable;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class StartableCloseableSafeLifecyleStrategyTest {
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private StartableCloseableSafeLifecyleStrategy underTest = new StartableCloseableSafeLifecyleStrategy();
+
+  @Test
+  public void start_calls_start_on_Startable_subclass() {
+    Startable startable = mock(Startable.class);
+
+    underTest.start(startable);
+
+    verify(startable).start();
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void start_calls_start_on_api_Startable_subclass() {
+    org.picocontainer.Startable startable = mock(org.picocontainer.Startable.class);
+
+    underTest.start(startable);
+
+    verify(startable).start();
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void start_does_not_call_stop_on_class_with_method_start_not_implementing_startable() {
+    Object startable = spy(new Object() {
+      public void start() {
+        // nothing to do
+      }
+    });
+
+    underTest.start(startable);
+
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void stop_calls_stop_on_Startable_subclass() {
+    Startable startable = mock(Startable.class);
+
+    underTest.stop(startable);
+
+    verify(startable).stop();
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void stop_calls_stop_on_api_Startable_subclass() {
+    org.picocontainer.Startable startable = mock(org.picocontainer.Startable.class);
+
+    underTest.stop(startable);
+
+    verify(startable).stop();
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void stop_does_not_call_stop_on_class_with_method_stop_not_implementing_startable() {
+    Object startable = spy(new Object() {
+      public void stop() {
+        // nothing to do
+      }
+    });
+
+    underTest.stop(startable);
+
+    verifyNoMoreInteractions(startable);
+  }
+
+  @Test
+  public void dispose_calls_close_on_Closeable_subclass() throws IOException {
+    Closeable closeable = mock(Closeable.class);
+
+    underTest.dispose(closeable);
+
+    verify(closeable).close();
+    verifyNoMoreInteractions(closeable);
+  }
+
+  @Test
+  public void dispose_calls_close_on_AutoCloseable_subclass() throws Exception {
+    AutoCloseable autoCloseable = mock(AutoCloseable.class);
+
+    underTest.dispose(autoCloseable);
+
+    verify(autoCloseable).close();
+    verifyNoMoreInteractions(autoCloseable);
+  }
+
+  @Test
+  public void dispose_does_not_call_close_on_class_with_method_close_not_implementing_Closeable_nor_AutoCloseable() {
+    Object closeable = spy(new Object() {
+      public void close() {
+        // nothing to do
+      }
+    });
+
+    underTest.dispose(closeable);
+
+    verifyNoMoreInteractions(closeable);
+  }
+
+  @Test
+  public void hasLifecycle_returns_true_on_Startable_and_subclass() {
+    Startable startable = mock(Startable.class);
+
+    assertThat(underTest.hasLifecycle(Startable.class)).isTrue();
+    assertThat(underTest.hasLifecycle(startable.getClass())).isTrue();
+  }
+
+  @Test
+  public void hasLifecycle_returns_true_on_api_Startable_and_subclass() {
+    org.picocontainer.Startable startable = mock(org.picocontainer.Startable.class);
+
+    assertThat(underTest.hasLifecycle(org.picocontainer.Startable.class)).isTrue();
+    assertThat(underTest.hasLifecycle(startable.getClass())).isTrue();
+  }
+
+  @Test
+  public void hasLifecycle_returns_true_on_api_Closeable_and_subclass() {
+    Closeable closeable = mock(Closeable.class);
+
+    assertThat(underTest.hasLifecycle(Closeable.class)).isTrue();
+    assertThat(underTest.hasLifecycle(closeable.getClass())).isTrue();
+  }
+
+  @Test
+  public void hasLifecycle_returns_true_on_api_AutoCloseable_and_subclass() {
+    AutoCloseable autoCloseable = mock(AutoCloseable.class);
+
+    assertThat(underTest.hasLifecycle(AutoCloseable.class)).isTrue();
+    assertThat(underTest.hasLifecycle(autoCloseable.getClass())).isTrue();
+  }
+
+  @Test
+  public void hasLifeCycle_returns_false_and_log_a_warning_for_type_defining_start_without_implementating_Startable() {
+    Object startable = new Object() {
+      public void start() {
+        // nothing to do
+      }
+    };
+
+    assertThat(underTest.hasLifecycle(startable.getClass())).isFalse();
+    verifyWarnLog(startable.getClass());
+  }
+
+  @Test
+  public void hasLifeCycle_returns_false_and_log_a_warning_for_type_defining_stop_without_implementating_Startable() {
+    Object startable = new Object() {
+      public void stop() {
+        // nothing to do
+      }
+    };
+
+    assertThat(underTest.hasLifecycle(startable.getClass())).isFalse();
+    verifyWarnLog(startable.getClass());
+  }
+
+  private void verifyWarnLog(Class<?> type) {
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN))
+      .contains("Component of type class " + type.getName() + " defines methods start() and/or stop(). Neither will be invoked to start/stop the component. " +
+        "Please implement either org.picocontainer.Startable or org.sonar.api.Startable");
+  }
+
+  @Test
+  public void hasLifeCycle_returns_false_and_log_a_warning_for_type_defining_close_without_implementating_Closeable_nor_AutoCloseable() {
+    Object startable = new Object() {
+      public void close() {
+        // nothing to do
+      }
+    };
+
+    assertThat(underTest.hasLifecycle(startable.getClass())).isFalse();
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN))
+      .contains("Component of type class " + startable.getClass().getName() + " defines method close(). It won't be invoked to dispose the component. " +
+        "Please implement either java.io.Closeable or java.lang.AutoCloseable");
+  }
+}
index eed8a5fed65480f6a655d6dac5657b70a5b6efbf..3a7836cff4fef903f31f20fd542d6c675e7bbe9d 100644 (file)
  */
 package org.sonar.api.utils.internal;
 
+import org.sonar.api.Startable;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.TempFolder;
 
 @ServerSide
-public class TempFolderCleaner {
+public class TempFolderCleaner implements Startable {
 
   private TempFolder defaultTempFolder;
 
@@ -35,6 +36,7 @@ public class TempFolderCleaner {
    * This method should not be renamed. It follows the naming convention
    * defined by IoC container.
    */
+  @Override
   public void start() {
     // Nothing to do
   }
@@ -43,6 +45,7 @@ public class TempFolderCleaner {
    * This method should not be renamed. It follows the naming convention
    * defined by IoC container.
    */
+  @Override
   public void stop() {
     ((DefaultTempFolder) defaultTempFolder).clean();
   }