aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2019-05-20 12:17:54 +0200
committerSonarTech <sonartech@sonarsource.com>2019-05-23 20:21:09 +0200
commit49451f5a6eb77a234072584192eaa576c78e3390 (patch)
tree1004708f085dcb2394951b12123451e90df80e11
parent84c34d5279752a7ec892db767e9aa092aadefc9f (diff)
downloadsonarqube-49451f5a6eb77a234072584192eaa576c78e3390.tar.gz
sonarqube-49451f5a6eb77a234072584192eaa576c78e3390.zip
SONAR-10034 drop support of reflection-based Pico Container lifecycle management
one must now implement org.picocontainer.Startable, org.sonar.api.Startable, java.io.Closeable and/or java.lang.Closeable
-rw-r--r--server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java10
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java7
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java10
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java11
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java105
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java60
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java149
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java51
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java218
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java5
17 files changed, 554 insertions, 126 deletions
diff --git a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java
index 52b78b22a06..9dc7160622f 100644
--- a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java
+++ b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/container/TaskContainerImpl.java
@@ -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
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
index fc368c72668..faa06886406 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
@@ -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();
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
index bcbb9ce67dd..bc09347827f 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
@@ -20,15 +20,14 @@
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
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java b/server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java
index eee20746290..fc1315a2d26 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/util/StoppableExecutorService.java
@@ -20,15 +20,16 @@
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();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java
index a9fe6290c08..88b1692362e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java
@@ -20,13 +20,13 @@
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
+ }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
index 8073e5a4c25..4b9df426660 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
@@ -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())));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java b/server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java
index 2279694159b..92a1f6e0829 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/startup/ClusterConfigurationCheck.java
@@ -43,6 +43,7 @@ public class ClusterConfigurationCheck implements Startable {
}
}
+ @Override
public void stop() {
// Nothing to do
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java
index f68c44aea50..39bdbf8edfb 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java
@@ -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)) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java
index e16a4cca99e..fd2ff4590cf 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java
@@ -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) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java
index a05840d0741..7be27df5c8a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/startup/RenameDeprecatedPropertyKeys.java
@@ -20,15 +20,16 @@
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
+ }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
index 6e57afd8ead..da2587ca9df 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
@@ -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
index 00000000000..a2b912bf722
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java
@@ -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
index 92c28c83b1f..00000000000
--- a/sonar-core/src/main/java/org/sonar/core/platform/StopSafeReflectionLifecycleStrategy.java
+++ /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);
- }
- }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
index 67bd7b63ff7..4584ce8991b 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
@@ -19,12 +19,14 @@
*/
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();
@@ -63,6 +65,37 @@ public class ComponentContainerTest {
}
@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();
final StartableStoppableComponent childComponent = 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
@@ -309,6 +346,16 @@ public class ComponentContainerTest {
}
@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();
StartableStoppableComponent component = new StartableStoppableComponent();
@@ -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;
}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
index 4cc7746b72d..0e168695980 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
@@ -19,11 +19,12 @@
*/
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
index 00000000000..4c92df1e01b
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java
@@ -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");
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java
index eed8a5fed65..3a7836cff4f 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/internal/TempFolderCleaner.java
@@ -19,11 +19,12 @@
*/
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();
}