one must now implement org.picocontainer.Startable, org.sonar.api.Startable, java.io.Closeable and/or java.lang.Closeable
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;
* 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
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;
* 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;
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();
}
}
- // do not rename. This naming convention is required for picocontainer.
+ @Override
public void stop() {
nanoHttpd.stop();
}
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;
* 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
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();
}
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) {
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.
MasterServletFilter.INSTANCE.initFilters(Arrays.asList(filters));
}
}
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
}
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;
* Synchronize Quality profiles during server startup
*/
@ServerSide
-public class RegisterQualityProfiles {
+public class RegisterQualityProfiles implements Startable {
private static final Logger LOGGER = Loggers.get(RegisterQualityProfiles.class);
this.system2 = system2;
}
+ @Override
public void start() {
List<BuiltInQProfile> builtInQProfiles = builtInQProfileRepository.get();
if (builtInQProfiles.isEmpty()) {
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())));
}
}
+ @Override
public void stop() {
// Nothing to do
}
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;
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);
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)) {
*/
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;
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";
this.defaultOrganizationProvider = defaultOrganizationProvider;
}
+ @Override
public void start() {
Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Register permission templates");
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) {
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;
this.definitions = definitions;
}
+ @Override
public void start() {
Loggers.get(RenameDeprecatedPropertyKeys.class).info("Rename deprecated property keys");
for (PropertyDefinition definition : definitions.getAll()) {
}
}
}
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
}
}
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() {
--- /dev/null
+/*
+ * 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;
+ }
+}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
*/
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;
}
@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();
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();
container.getComponentByType(FailingStopWithOOMComponent2.class)
};
+ container.stopComponents();
+
+ assertThat(container.getPicoContainer().getLifecycleState().isDisposed()).isTrue();
+ Arrays.stream(components).forEach(cpt -> assertThat(cpt.stopped).isTrue());
}
@Test
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();
* 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);
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() {
}
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;
}
*/
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;
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
@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
}
}
+
}
--- /dev/null
+/*
+ * 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");
+ }
+}
*/
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;
* This method should not be renamed. It follows the naming convention
* defined by IoC container.
*/
+ @Override
public void start() {
// Nothing to do
}
* This method should not be renamed. It follows the naming convention
* defined by IoC container.
*/
+ @Override
public void stop() {
((DefaultTempFolder) defaultTempFolder).clean();
}