one must now implement org.picocontainer.Startable, org.sonar.api.Startable, java.io.Closeable and/or java.lang.Closeabletags/7.8
@@ -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 |
@@ -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(); | |||
} |
@@ -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 |
@@ -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(); | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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()))); |
@@ -43,6 +43,7 @@ public class ClusterConfigurationCheck implements Startable { | |||
} | |||
} | |||
@Override | |||
public void stop() { | |||
// Nothing to do | |||
} |
@@ -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)) { |
@@ -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) { |
@@ -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 | |||
} | |||
} |
@@ -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() { |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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(); | |||
@@ -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; | |||
} |
@@ -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 | |||
} | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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(); | |||
} |