aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core/src
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2022-02-01 15:16:25 -0600
committersonartech <sonartech@sonarsource.com>2022-02-22 20:02:46 +0000
commit60c1a4038e041a342dda9810e6fd761d66b01bdb (patch)
tree0e76b4252e4d7d257cf4ddcb6f081996bb1e03ab /sonar-core/src
parent9694d4113bf401b84e86e0223dbea8f5339388d8 (diff)
downloadsonarqube-60c1a4038e041a342dda9810e6fd761d66b01bdb.tar.gz
sonarqube-60c1a4038e041a342dda9810e6fd761d66b01bdb.zip
SONAR-15994 Migrate Sonarqube IOC framework from Pico to Spring
Diffstat (limited to 'sonar-core/src')
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java2
-rw-r--r--sonar-core/src/main/java/org/sonar/core/language/LanguagesProvider.java38
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ClassDerivedBeanDefinition.java64
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java339
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/Container.java5
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/LazyStrategy.java30
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/LazyUnlessStartableStrategy.java (renamed from sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java)29
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ListContainer.java95
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/Module.java8
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PriorityBeanFactory.java138
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/SpringComponentContainer.java260
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/SpringInitStrategy.java39
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/StartableBeanPostProcessor.java55
-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/StartableContainer.java24
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java2
-rw-r--r--sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java140
-rw-r--r--sonar-core/src/test/java/org/sonar/core/language/LanguagesProviderTest.java61
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java584
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/LazyUnlessStartableStrategyTest.java41
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ListContainerTest.java89
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ModuleTest.java13
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java100
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PriorityBeanFactoryTest.java103
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/SpringComponentContainerTest.java358
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/StartableBeanPostProcessorTest.java55
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java218
28 files changed, 1504 insertions, 1494 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java
index bae7811e160..cc436fbc0d4 100644
--- a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java
@@ -79,8 +79,7 @@ public abstract class CoreExtensionsInstaller {
}
}
- private void addDeclaredExtensions(ExtensionContainer container, Predicate<Object> extensionFilter,
- Predicate<Object> additionalSideFilter, CoreExtension coreExtension) {
+ private void addDeclaredExtensions(ExtensionContainer container, Predicate<Object> extensionFilter, Predicate<Object> additionalSideFilter, CoreExtension coreExtension) {
ContextImpl context = new ContextImpl(container, extensionFilter, additionalSideFilter, coreExtension.getName());
coreExtension.load(context);
}
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
index ad64a703f3f..cfa5ac20de9 100644
--- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
@@ -40,7 +40,7 @@ import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
-import org.picocontainer.Startable;
+import org.sonar.api.Startable;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
diff --git a/sonar-core/src/main/java/org/sonar/core/language/LanguagesProvider.java b/sonar-core/src/main/java/org/sonar/core/language/LanguagesProvider.java
new file mode 100644
index 00000000000..811cd624c48
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/language/LanguagesProvider.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.language;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.springframework.context.annotation.Bean;
+
+public class LanguagesProvider {
+ @Bean("Languages")
+ public Languages provide(Optional<List<Language>> languages) {
+ if (languages.isPresent()) {
+ return new Languages(languages.get().toArray(new Language[0]));
+ } else {
+ return new Languages();
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ClassDerivedBeanDefinition.java b/sonar-core/src/main/java/org/sonar/core/platform/ClassDerivedBeanDefinition.java
new file mode 100644
index 00000000000..7ea16397e60
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ClassDerivedBeanDefinition.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.lang.reflect.Constructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.lang.Nullable;
+
+/**
+ * Taken from Spring's GenericApplicationContext.ClassDerivedBeanDefinition.
+ * The goal is to support multiple constructors when adding extensions for plugins when no annotations are present.
+ * Spring will pick the constructor with the highest number of arguments that it can inject.
+ */
+public class ClassDerivedBeanDefinition extends RootBeanDefinition {
+ public ClassDerivedBeanDefinition(Class<?> beanClass) {
+ super(beanClass);
+ }
+
+ public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) {
+ super(original);
+ }
+
+ /**
+ * This method gets called from AbstractAutowireCapableBeanFactory#createBeanInstance when a bean is instantiated.
+ * It first tries to look at annotations or any other methods provided by bean post processors. If a constructor can't be determined, it will fallback to this method.
+ */
+ @Override
+ @Nullable
+ public Constructor<?>[] getPreferredConstructors() {
+ Class<?> clazz = getBeanClass();
+ Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
+ if (primaryCtor != null) {
+ return new Constructor<?>[] {primaryCtor};
+ }
+ Constructor<?>[] publicCtors = clazz.getConstructors();
+ if (publicCtors.length > 0) {
+ return publicCtors;
+ }
+ return null;
+ }
+
+ @Override
+ public RootBeanDefinition cloneBeanDefinition() {
+ return new ClassDerivedBeanDefinition(this);
+ }
+}
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
deleted file mode 100644
index b8d7c56b310..00000000000
--- a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.picocontainer.Characteristics;
-import org.picocontainer.ComponentAdapter;
-import org.picocontainer.ComponentFactory;
-import org.picocontainer.ComponentMonitor;
-import org.picocontainer.DefaultPicoContainer;
-import org.picocontainer.LifecycleStrategy;
-import org.picocontainer.MutablePicoContainer;
-import org.picocontainer.PicoContainer;
-import org.picocontainer.behaviors.OptInCaching;
-import org.picocontainer.monitors.NullComponentMonitor;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.scanner.ScannerSide;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.System2;
-
-import static com.google.common.collect.ImmutableList.copyOf;
-import static java.util.Objects.requireNonNull;
-import static java.util.Optional.ofNullable;
-
-@ScannerSide
-@ServerSide
-@ComputeEngineSide
-public class ComponentContainer implements ExtensionContainer {
- public static final int COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER = 2;
-
- private static final class ExtendedDefaultPicoContainer extends DefaultPicoContainer {
- private ExtendedDefaultPicoContainer(ComponentFactory componentFactory, LifecycleStrategy lifecycleStrategy,
- @Nullable PicoContainer parent, ComponentMonitor componentMonitor) {
- super(componentFactory, lifecycleStrategy, parent, componentMonitor);
- }
-
- @Override
- public Object getComponent(final Object componentKeyOrType, final Class<? extends Annotation> annotation) {
- try {
- return super.getComponent(componentKeyOrType, annotation);
- } catch (Throwable t) {
- throw new IllegalStateException("Unable to load component " + componentKeyOrType, t);
- }
- }
-
- @Override
- public MutablePicoContainer makeChildContainer() {
- DefaultPicoContainer pc = new ExtendedDefaultPicoContainer(componentFactory, lifecycleStrategy, this, componentMonitor);
- addChildContainer(pc);
- return pc;
- }
- }
-
- private ComponentContainer parent;
- private final List<ComponentContainer> children = new ArrayList<>();
- private MutablePicoContainer pico;
- private PropertyDefinitions propertyDefinitions;
- private ComponentKeys componentKeys;
-
- /**
- * Create root container
- */
- public ComponentContainer() {
- this(createPicoContainer());
- }
-
- protected ComponentContainer(MutablePicoContainer picoContainer) {
- this(picoContainer, new PropertyDefinitions(System2.INSTANCE));
- }
-
- protected ComponentContainer(MutablePicoContainer picoContainer, PropertyDefinitions propertyDefinitions) {
- requireNonNull(propertyDefinitions, "PropertyDefinitions can not be null");
- this.parent = null;
- this.pico = picoContainer;
- this.componentKeys = new ComponentKeys();
- this.propertyDefinitions = propertyDefinitions;
- addSingleton(propertyDefinitions);
- addSingleton(this);
- }
-
- /**
- * Create child container
- */
- protected ComponentContainer(ComponentContainer parent) {
- this.parent = parent;
- this.pico = parent.pico.makeChildContainer();
- this.parent.children.add(this);
- this.propertyDefinitions = parent.propertyDefinitions;
- this.componentKeys = new ComponentKeys();
- addSingleton(this);
- }
-
- protected void setParent(ComponentContainer parent) {
- this.parent = parent;
- }
-
- public void execute() {
- try {
- startComponents();
- } finally {
- stopComponents();
- }
- }
-
- /**
- * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting
- * a component twice is not authorized.
- */
- public ComponentContainer startComponents() {
- try {
- doBeforeStart();
- pico.start();
- doAfterStart();
- return this;
- } catch (Exception e) {
- throw PicoUtils.propagate(e);
- }
- }
-
- /**
- * This method aims to be overridden
- */
- protected void doBeforeStart() {
- // nothing
- }
-
- /**
- * This method aims to be overridden
- */
- protected void doAfterStart() {
- // nothing
- }
-
- /**
- * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting
- * a component twice is not authorized.
- */
- public ComponentContainer stopComponents() {
- try {
- stopChildren();
- if (pico.getLifecycleState().isStarted()) {
- pico.stop();
- }
- pico.dispose();
- } finally {
- if (parent != null) {
- parent.removeChild(this);
- }
- }
- return this;
- }
-
- private void stopChildren() {
- // loop over a copy of list of children in reverse order, both to stop last added child first and because children
- // remove themselves from the list of children of their parent (ie. changing this.children)
- Lists.reverse(new ArrayList<>(this.children))
- .forEach(ComponentContainer::stopComponents);
- }
-
- /**
- * @since 3.5
- */
- @Override
- public ComponentContainer add(Object... objects) {
- for (Object object : objects) {
- if (object instanceof ComponentAdapter) {
- addPicoAdapter((ComponentAdapter) object);
- } else if (object instanceof Iterable) {
- add(Iterables.toArray((Iterable) object, Object.class));
- } else {
- addSingleton(object);
- }
- }
- return this;
- }
-
- public void addIfMissing(Object object, Class<?> objectType) {
- if (getComponentByType(objectType) == null) {
- add(object);
- }
- }
-
- @Override
- public ComponentContainer addSingletons(Iterable<?> components) {
- for (Object component : components) {
- addSingleton(component);
- }
- return this;
- }
-
- public ComponentContainer addSingleton(Object component) {
- return addComponent(component, true);
- }
-
- /**
- * @param singleton return always the same instance if true, else a new instance
- * is returned each time the component is requested
- */
- public ComponentContainer addComponent(Object component, boolean singleton) {
- Object key = componentKeys.of(component);
- if (component instanceof ComponentAdapter) {
- pico.addAdapter((ComponentAdapter) component);
- } else {
- try {
- pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component);
- } catch (Throwable t) {
- throw new IllegalStateException("Unable to register component " + getName(component), t);
- }
- declareExtension("", component);
- }
- return this;
- }
-
- @Override
- public ComponentContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) {
- Object key = componentKeys.of(extension);
- try {
- pico.as(Characteristics.CACHE).addComponent(key, extension);
- } catch (Throwable t) {
- throw new IllegalStateException("Unable to register extension " + getName(extension) + (pluginInfo != null ? (" from plugin '" + pluginInfo.getKey() + "'") : ""), t);
- }
- declareExtension(pluginInfo, extension);
- return this;
- }
-
- @Override
- public ComponentContainer addExtension(@Nullable String defaultCategory, Object extension) {
- Object key = componentKeys.of(extension);
- try {
- pico.as(Characteristics.CACHE).addComponent(key, extension);
- } catch (Throwable t) {
- throw new IllegalStateException("Unable to register extension " + getName(extension), t);
- }
- declareExtension(defaultCategory, extension);
- return this;
- }
-
- private static String getName(Object extension) {
- if (extension instanceof Class) {
- return ((Class<?>) extension).getName();
- }
- return getName(extension.getClass());
- }
-
- @Override
- public ComponentContainer declareExtension(@Nullable PluginInfo pluginInfo, Object extension) {
- declareExtension(pluginInfo != null ? pluginInfo.getName() : "", extension);
- return this;
- }
-
- @Override
- public ComponentContainer declareExtension(@Nullable String defaultCategory, Object extension) {
- propertyDefinitions.addComponent(extension, ofNullable(defaultCategory).orElse(""));
- return this;
- }
-
- public ComponentContainer addPicoAdapter(ComponentAdapter<?> adapter) {
- pico.addAdapter(adapter);
- return this;
- }
-
- @Override
- public <T> T getComponentByType(Class<T> type) {
- return pico.getComponent(type);
- }
-
- public Object getComponentByKey(Object key) {
- return pico.getComponent(key);
- }
-
- @Override
- public <T> List<T> getComponentsByType(Class<T> tClass) {
- return pico.getComponents(tClass);
- }
-
- public ComponentContainer removeChild(ComponentContainer childToBeRemoved) {
- requireNonNull(childToBeRemoved);
- Iterator<ComponentContainer> childrenIterator = children.iterator();
- while (childrenIterator.hasNext()) {
- ComponentContainer child = childrenIterator.next();
- if (child == childToBeRemoved) {
- if (pico.removeChildContainer(child.pico)) {
- childrenIterator.remove();
- }
- break;
- }
- }
- return this;
- }
-
- public ComponentContainer createChild() {
- return new ComponentContainer(this);
- }
-
- public static MutablePicoContainer createPicoContainer() {
- return new ExtendedDefaultPicoContainer(new OptInCaching(), new StartableCloseableSafeLifecyleStrategy(), null, new NullComponentMonitor());
- }
-
- public ComponentContainer getParent() {
- return parent;
- }
-
- public List<ComponentContainer> getChildren() {
- return copyOf(children);
- }
-
- public MutablePicoContainer getPicoContainer() {
- return pico;
- }
-
- public int size() {
- return pico.getComponentAdapters().size();
- }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/Container.java b/sonar-core/src/main/java/org/sonar/core/platform/Container.java
index e7a9963623d..eb9b03789ff 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/Container.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/Container.java
@@ -20,14 +20,15 @@
package org.sonar.core.platform;
import java.util.List;
+import java.util.Optional;
public interface Container {
Container add(Object... objects);
- Container addSingletons(Iterable<?> components);
-
<T> T getComponentByType(Class<T> type);
+ <T> Optional<T> getOptionalComponentByType(Class<T> type);
+
<T> List<T> getComponentsByType(Class<T> type);
Container getParent();
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/LazyStrategy.java b/sonar-core/src/main/java/org/sonar/core/platform/LazyStrategy.java
new file mode 100644
index 00000000000..85c3f4d8cba
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/LazyStrategy.java
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 javax.annotation.Nullable;
+import org.springframework.beans.factory.config.BeanDefinition;
+
+public class LazyStrategy extends SpringInitStrategy {
+ @Override
+ protected boolean isLazyInit(BeanDefinition beanDefinition, @Nullable Class<?> clazz) {
+ return true;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/LazyUnlessStartableStrategy.java
index c73bb07ea6b..307881a89c8 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/LazyUnlessStartableStrategy.java
@@ -19,29 +19,14 @@
*/
package org.sonar.core.platform;
-import com.google.common.base.Throwables;
-import org.picocontainer.PicoLifecycleException;
+import org.sonar.api.Startable;
+import org.springframework.beans.factory.config.BeanDefinition;
-class PicoUtils {
+import javax.annotation.Nullable;
- private PicoUtils() {
- // only static methods
- }
-
- static Throwable sanitize(Throwable t) {
- Throwable result = t;
- Throwable cause = t.getCause();
- if (t instanceof PicoLifecycleException && cause != null) {
- if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) {
- result = cause.getCause();
- } else {
- result = cause;
- }
- }
- return result;
- }
-
- static RuntimeException propagate(Throwable t) {
- throw Throwables.propagate(sanitize(t));
+public class LazyUnlessStartableStrategy extends SpringInitStrategy {
+ @Override
+ protected boolean isLazyInit(BeanDefinition beanDefinition, @Nullable Class<?> clazz) {
+ return clazz == null || !Startable.class.isAssignableFrom(clazz);
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ListContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ListContainer.java
new file mode 100644
index 00000000000..eaf8a16e70e
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ListContainer.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+import static java.util.Collections.unmodifiableList;
+
+/**
+ * Intended to be used in tests
+ */
+public class ListContainer implements ExtensionContainer {
+ private final List<Object> objects = new ArrayList<>();
+
+ @Override
+ public Container add(Object... objects) {
+ for (Object o : objects) {
+ if (o instanceof Module) {
+ ((Module) o).configure(this);
+ } else if (o instanceof Iterable) {
+ add(Iterables.toArray((Iterable<?>) o, Object.class));
+ } else {
+ this.objects.add(o);
+ }
+ }
+ return this;
+ }
+
+ public List<Object> getAddedObjects() {
+ return unmodifiableList(new ArrayList<>(objects));
+ }
+
+ @Override
+ public <T> T getComponentByType(Class<T> type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Optional<T> getOptionalComponentByType(Class<T> type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> List<T> getComponentsByType(Class<T> type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ExtensionContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) {
+ add(extension);
+ return this;
+ }
+
+ @Override
+ public ExtensionContainer addExtension(@Nullable String defaultCategory, Object extension) {
+ add(extension);
+ return this;
+ }
+
+ @Override
+ public ExtensionContainer declareExtension(@Nullable PluginInfo pluginInfo, Object extension) {
+ return this;
+ }
+
+ @Override
+ public ExtensionContainer declareExtension(@Nullable String defaultCategory, Object extension) {
+ return this;
+ }
+
+ @Override
+ public ExtensionContainer getParent() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/Module.java b/sonar-core/src/main/java/org/sonar/core/platform/Module.java
index 2e94f28c50e..383aa6be3e2 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/Module.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/Module.java
@@ -24,13 +24,11 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class Module {
- private ComponentContainer container;
+ private Container container;
- public Module configure(ComponentContainer container) {
+ public Module configure(Container container) {
this.container = checkNotNull(container);
-
configureModule();
-
return this;
}
@@ -43,7 +41,7 @@ public abstract class Module {
for (Object object : objects) {
if (object != null) {
- container.addComponent(object, true);
+ container.add(object);
}
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PriorityBeanFactory.java b/sonar-core/src/main/java/org/sonar/core/platform/PriorityBeanFactory.java
new file mode 100644
index 00000000000..a6af0bbb5de
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PriorityBeanFactory.java
@@ -0,0 +1,138 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+
+public class PriorityBeanFactory extends DefaultListableBeanFactory {
+ /**
+ * Determines highest priority of the bean candidates.
+ * Does not take into account the @Primary annotations.
+ * This gets called from {@link DefaultListableBeanFactory#determineAutowireCandidate} when the bean factory is finding the beans to autowire. That method
+ * checks for @Primary before calling this method.
+ *
+ * The strategy is to look at the @Priority annotations. If there are ties, we give priority to components that were added to child containers over their parents.
+ * If there are still ties, null is returned, which will ultimately cause Spring to throw a NoUniqueBeanDefinitionException.
+ */
+ @Override
+ @Nullable
+ protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
+ List<Bean> candidateBeans = candidates.entrySet().stream()
+ .filter(e -> e.getValue() != null)
+ .map(e -> new Bean(e.getKey(), e.getValue()))
+ .collect(Collectors.toList());
+
+ List<Bean> beansAfterPriority = highestPriority(candidateBeans, b -> getPriority(b.getInstance()));
+ if (beansAfterPriority.isEmpty()) {
+ return null;
+ } else if (beansAfterPriority.size() == 1) {
+ return beansAfterPriority.get(0).getName();
+ }
+
+ List<Bean> beansAfterHierarchy = highestPriority(beansAfterPriority, b -> getHierarchyPriority(b.getName()));
+ if (beansAfterHierarchy.size() == 1) {
+ return beansAfterHierarchy.get(0).getName();
+ }
+
+ return null;
+ }
+
+ private static List<Bean> highestPriority(List<Bean> candidates, PriorityFunction function) {
+ List<Bean> highestPriorityBeans = new ArrayList<>();
+ Integer highestPriority = null;
+
+ for (Bean candidate : candidates) {
+ Integer candidatePriority = function.classify(candidate);
+ if (candidatePriority == null) {
+ candidatePriority = Integer.MAX_VALUE;
+ }
+ if (highestPriority == null) {
+ highestPriority = candidatePriority;
+ highestPriorityBeans.add(candidate);
+ } else if (candidatePriority < highestPriority) {
+ highestPriorityBeans.clear();
+ highestPriority = candidatePriority;
+ highestPriorityBeans.add(candidate);
+ } else if (candidatePriority.equals(highestPriority)) {
+ highestPriorityBeans.add(candidate);
+ }
+ }
+ return highestPriorityBeans;
+ }
+
+ @CheckForNull
+ private Integer getHierarchyPriority(String beanName) {
+ DefaultListableBeanFactory factory = this;
+ int i = 1;
+ while (factory != null) {
+ if (factory.containsBeanDefinition(beanName)) {
+ return i;
+ }
+ factory = (DefaultListableBeanFactory) factory.getParentBeanFactory();
+ i++;
+ }
+ return null;
+ }
+
+ /**
+ * A common mistake when migrating from Pico Container to Spring is to forget to add @Inject or @Autowire annotations to classes that have multiple constructors.
+ * Spring will fail if there is no default no-arg constructor, but it will silently use the no-arg constructor if there is one, never calling the other constructors.
+ * We override this method to fail fast if a class has multiple constructors.
+ */
+ @Override
+ protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
+ if (mbd.hasBeanClass() && mbd.getBeanClass().getConstructors().length > 1) {
+ throw new IllegalStateException("Constructor annotations missing in: " + mbd.getBeanClass());
+ }
+ return super.instantiateBean(beanName, mbd);
+ }
+
+ private static class Bean {
+ private final String name;
+ private final Object instance;
+
+ public Bean(String name, Object instance) {
+ this.name = name;
+ this.instance = instance;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getInstance() {
+ return instance;
+ }
+ }
+
+ @FunctionalInterface
+ private interface PriorityFunction {
+ @CheckForNull
+ Integer classify(Bean candidate);
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/SpringComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/SpringComponentContainer.java
new file mode 100644
index 00000000000..44f0db99eed
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/SpringComponentContainer.java
@@ -0,0 +1,260 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.utils.System2;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static java.util.Collections.emptyList;
+import static java.util.Optional.ofNullable;
+
+public class SpringComponentContainer implements StartableContainer {
+ protected final AnnotationConfigApplicationContext context;
+ @Nullable
+ protected final SpringComponentContainer parent;
+ protected final List<SpringComponentContainer> children = new ArrayList<>();
+
+ private final PropertyDefinitions propertyDefinitions;
+ private final ComponentKeys componentKeys = new ComponentKeys();
+
+ public SpringComponentContainer() {
+ this(null, new PropertyDefinitions(System2.INSTANCE), emptyList(), new LazyUnlessStartableStrategy());
+ }
+
+ protected SpringComponentContainer(List<?> externalExtensions) {
+ this(null, new PropertyDefinitions(System2.INSTANCE), externalExtensions, new LazyUnlessStartableStrategy());
+ }
+
+ protected SpringComponentContainer(SpringComponentContainer parent) {
+ this(parent, parent.propertyDefinitions, emptyList(), new LazyUnlessStartableStrategy());
+ }
+
+ protected SpringComponentContainer(SpringComponentContainer parent, SpringInitStrategy initStrategy) {
+ this(parent, parent.propertyDefinitions, emptyList(), initStrategy);
+ }
+
+ protected SpringComponentContainer(@Nullable SpringComponentContainer parent, PropertyDefinitions propertyDefs, List<?> externalExtensions, SpringInitStrategy initStrategy) {
+ this.parent = parent;
+ this.propertyDefinitions = propertyDefs;
+ this.context = new AnnotationConfigApplicationContext(new PriorityBeanFactory());
+ this.context.setAllowBeanDefinitionOverriding(false);
+ ((AbstractAutowireCapableBeanFactory) context.getBeanFactory()).setParameterNameDiscoverer(null);
+ if (parent != null) {
+ context.setParent(parent.context);
+ parent.children.add(this);
+ }
+ add(initStrategy);
+ add(this);
+ add(new StartableBeanPostProcessor());
+ add(externalExtensions);
+ add(propertyDefs);
+ }
+
+ /**
+ * Beans need to have a unique name, otherwise they'll override each other.
+ * The strategy is:
+ * - For classes, use the classloader + fully qualified class name as the name of the bean
+ * - For instances, use the Classloader + FQCN + toString()
+ * - If the object is a collection, iterate through the elements and apply the same strategy for each of them
+ */
+ @Override
+ public Container add(Object... objects) {
+ for (Object o : objects) {
+ if (o instanceof Class) {
+ Class<?> clazz = (Class<?>) o;
+ if (Module.class.isAssignableFrom(clazz)) {
+ throw new IllegalStateException("Modules should be added as instances");
+ }
+ context.registerBean(componentKeys.ofClass(clazz), clazz);
+ declareExtension("", o);
+ } else if (o instanceof Module) {
+ ((Module) o).configure(this);
+ } else if (o instanceof Iterable) {
+ add(Iterables.toArray((Iterable<?>) o, Object.class));
+ } else {
+ registerInstance(o);
+ declareExtension("", o);
+ }
+ }
+ return this;
+ }
+
+ private <T> void registerInstance(T instance) {
+ Supplier<T> supplier = () -> instance;
+ Class<T> clazz = (Class<T>) instance.getClass();
+ context.registerBean(componentKeys.ofInstance(instance), clazz, supplier);
+ }
+
+ /**
+ * Extensions are usually added by plugins and we assume they don't support any injection-related annotations.
+ * Spring contexts supporting annotations will fail if multiple constructors are present without any annotations indicating which one to use for injection.
+ * For that reason, we need to create the beans ourselves, using ClassDerivedBeanDefinition, which will declare that all constructors can be used for injection.
+ */
+ private Container addExtension(Object o) {
+ if (o instanceof Class) {
+ Class<?> clazz = (Class<?>) o;
+ ClassDerivedBeanDefinition bd = new ClassDerivedBeanDefinition(clazz);
+ context.registerBeanDefinition(componentKeys.ofClass(clazz), bd);
+ } else if (o instanceof Iterable) {
+ ((Iterable<?>) o).forEach(this::addExtension);
+ } else {
+ registerInstance(o);
+ }
+ return this;
+ }
+
+ @Override
+ public <T> T getComponentByType(Class<T> type) {
+ try {
+ return context.getBean(type);
+ } catch (Exception t) {
+ throw new IllegalStateException("Unable to load component " + type, t);
+ }
+ }
+
+ @Override public <T> Optional<T> getOptionalComponentByType(Class<T> type) {
+ try {
+ return Optional.of(context.getBean(type));
+ } catch (NoSuchBeanDefinitionException t) {
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public <T> List<T> getComponentsByType(Class<T> type) {
+ try {
+ return new ArrayList<>(context.getBeansOfType(type).values());
+ } catch (Exception t) {
+ throw new IllegalStateException("Unable to load components " + type, t);
+ }
+ }
+
+ public AnnotationConfigApplicationContext context() {
+ return context;
+ }
+
+ public void execute() {
+ RuntimeException r = null;
+ try {
+ startComponents();
+ } catch (RuntimeException e) {
+ r = e;
+ } finally {
+ try {
+ stopComponents();
+ } catch (RuntimeException e) {
+ if (r == null) {
+ r = e;
+ }
+ }
+ }
+ if (r != null) {
+ throw r;
+ }
+ }
+
+ @Override
+ public SpringComponentContainer startComponents() {
+ doBeforeStart();
+ context.refresh();
+ doAfterStart();
+ return this;
+ }
+
+ public SpringComponentContainer stopComponents() {
+ try {
+ stopChildren();
+ if (context.isActive()) {
+ context.close();
+ }
+ } finally {
+ if (parent != null) {
+ parent.children.remove(this);
+ }
+ }
+ return this;
+ }
+
+ private void stopChildren() {
+ // loop over a copy of list of children in reverse order
+ Lists.reverse(new ArrayList<>(this.children)).forEach(SpringComponentContainer::stopComponents);
+ }
+
+ public SpringComponentContainer createChild() {
+ return new SpringComponentContainer(this);
+ }
+
+ @Override
+ @CheckForNull
+ public SpringComponentContainer getParent() {
+ return parent;
+ }
+
+ @Override
+ public SpringComponentContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) {
+ addExtension(extension);
+ declareExtension(pluginInfo, extension);
+ return this;
+ }
+
+ @Override
+ public SpringComponentContainer addExtension(@Nullable String defaultCategory, Object extension) {
+ addExtension(extension);
+ declareExtension(defaultCategory, extension);
+ return this;
+ }
+
+ @Override
+ public SpringComponentContainer declareExtension(@Nullable PluginInfo pluginInfo, Object extension) {
+ declareExtension(pluginInfo != null ? pluginInfo.getName() : "", extension);
+ return this;
+ }
+
+ @Override
+ public SpringComponentContainer declareExtension(@Nullable String defaultCategory, Object extension) {
+ this.propertyDefinitions.addComponent(extension, ofNullable(defaultCategory).orElse(""));
+ return this;
+ }
+
+ /**
+ * This method aims to be overridden
+ */
+ protected void doBeforeStart() {
+ // nothing
+ }
+
+ /**
+ * This method aims to be overridden
+ */
+ protected void doAfterStart() {
+ // nothing
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/SpringInitStrategy.java b/sonar-core/src/main/java/org/sonar/core/platform/SpringInitStrategy.java
new file mode 100644
index 00000000000..107c38709ba
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/SpringInitStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 javax.annotation.Nullable;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+public abstract class SpringInitStrategy implements BeanFactoryPostProcessor {
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ for (String beanName : beanFactory.getBeanDefinitionNames()) {
+ BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
+ Class<?> rawClass = beanDefinition.getResolvableType().getRawClass();
+ beanDefinition.setLazyInit(isLazyInit(beanDefinition, rawClass));
+ }
+ }
+
+ protected abstract boolean isLazyInit(BeanDefinition beanDefinition, @Nullable Class<?> clazz);
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/StartableBeanPostProcessor.java b/sonar-core/src/main/java/org/sonar/core/platform/StartableBeanPostProcessor.java
new file mode 100644
index 00000000000..a4aa6ea94e7
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/StartableBeanPostProcessor.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.sonar.api.Startable;
+import org.sonar.api.utils.log.Loggers;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
+import org.springframework.lang.Nullable;
+
+public class StartableBeanPostProcessor implements DestructionAwareBeanPostProcessor {
+ @Override
+ @Nullable
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof Startable) {
+ ((Startable) bean).start();
+ }
+ return bean;
+ }
+
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return bean instanceof Startable;
+ }
+
+ @Override
+ public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
+ try {
+ // note: Spring will call close() on AutoCloseable beans.
+ if (bean instanceof Startable) {
+ ((Startable) bean).stop();
+ }
+ } catch (Exception e) {
+ Loggers.get(StartableBeanPostProcessor.class)
+ .warn("Dispose of component {} failed", bean.getClass().getCanonicalName(), e);
+ }
+ }
+}
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
deleted file mode 100644
index 9549af80a27..00000000000
--- a/sonar-core/src/main/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategy.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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/StartableContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/StartableContainer.java
new file mode 100644
index 00000000000..2cf7b83ba77
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/StartableContainer.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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;
+
+public interface StartableContainer extends ExtensionContainer {
+ StartableContainer startComponents();
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java b/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java
index 45272778ea6..1257dd4b485 100644
--- a/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java
+++ b/sonar-core/src/main/java/org/sonar/core/util/UuidFactoryImpl.java
@@ -27,7 +27,7 @@ public enum UuidFactoryImpl implements UuidFactory {
/**
* Should be removed as long {@link Uuids} is not used anymore. {@code UuidFactoryImpl}
- * should be built by picocontainer through a public constructor.
+ * should be injected by the ioc container through a public constructor.
*/
INSTANCE;
diff --git a/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java
index 35bdea07ffd..094d1655733 100644
--- a/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java
@@ -39,42 +39,39 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
-import org.picocontainer.ComponentAdapter;
import org.sonar.api.Property;
import org.sonar.api.SonarRuntime;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
-import org.sonar.core.platform.ComponentContainer;
+import org.sonar.core.platform.ExtensionContainer;
+import org.sonar.core.platform.ListContainer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.extension.CoreExtensionsInstaller.noAdditionalSideFilter;
import static org.sonar.core.extension.CoreExtensionsInstaller.noExtensionFilter;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
@RunWith(DataProviderRunner.class)
public class CoreExtensionsInstallerTest {
- private SonarRuntime sonarRuntime = mock(SonarRuntime.class);
- private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
- private CoreExtensionsInstaller underTest = new CoreExtensionsInstaller(sonarRuntime, coreExtensionRepository, WestSide.class) {
+ private final SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private final CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ private final CoreExtensionsInstaller underTest = new CoreExtensionsInstaller(sonarRuntime, coreExtensionRepository, WestSide.class) {
};
- private ArgumentCaptor<CoreExtension.Context> contextCaptor = ArgumentCaptor.forClass(CoreExtension.Context.class);
+ private final ArgumentCaptor<CoreExtension.Context> contextCaptor = ArgumentCaptor.forClass(CoreExtension.Context.class);
private static int name_counter = 0;
@Test
public void install_has_no_effect_if_CoreExtensionRepository_has_no_loaded_CoreExtension() {
- ComponentContainer container = new ComponentContainer();
-
+ ListContainer container = new ListContainer();
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
-
assertAddedExtensions(container, 0);
}
@@ -87,7 +84,7 @@ public class CoreExtensionsInstallerTest {
List<CoreExtension> coreExtensions = ImmutableList.of(coreExtension1, coreExtension2, coreExtension3, coreExtension4);
InOrder inOrder = Mockito.inOrder(coreExtension1, coreExtension2, coreExtension3, coreExtension4);
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(coreExtensions.stream());
- ComponentContainer container = new ComponentContainer();
+ ListContainer container = new ListContainer();
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
@@ -105,7 +102,7 @@ public class CoreExtensionsInstallerTest {
CoreExtension coreExtension1 = newCoreExtension();
CoreExtension coreExtension2 = newCoreExtension();
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
- ComponentContainer container = new ComponentContainer();
+ ListContainer container = new ListContainer();
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
@@ -121,7 +118,7 @@ public class CoreExtensionsInstallerTest {
CoreExtension coreExtension1 = newCoreExtension();
CoreExtension coreExtension2 = newCoreExtension();
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
- ComponentContainer container = new ComponentContainer();
+ ListContainer container = new ListContainer();
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
@@ -138,9 +135,8 @@ public class CoreExtensionsInstallerTest {
CoreExtension coreExtension2 = newCoreExtension();
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
Configuration configuration = new MapSettings().asConfig();
- ComponentContainer container = new ComponentContainer();
- container.add(configuration);
-
+ ExtensionContainer container = mock(ExtensionContainer.class);
+ when(container.getComponentByType(Configuration.class)).thenReturn(configuration);
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
verify(coreExtension1).load(contextCaptor.capture());
@@ -156,12 +152,15 @@ public class CoreExtensionsInstallerTest {
List<Object> extensions = ImmutableList.of(WestSideClass.class, EastSideClass.class, OtherSideClass.class, Latitude.class);
CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
- ComponentContainer container = new ComponentContainer();
+ ExtensionContainer container = mock(ExtensionContainer.class);
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
- assertAddedExtensions(container, WestSideClass.class, Latitude.class);
- assertPropertyDefinitions(container);
+ verify(container).addExtension(coreExtension.getName(), WestSideClass.class);
+ verify(container).declareExtension(coreExtension.getName(), OtherSideClass.class);
+ verify(container).declareExtension(coreExtension.getName(), EastSideClass.class);
+ verify(container).addExtension(coreExtension.getName(), Latitude.class);
+ verifyNoMoreInteractions(container);
}
@Test
@@ -170,42 +169,15 @@ public class CoreExtensionsInstallerTest {
List<Object> extensions = ImmutableList.of(WestSideClass.class, EastSideClass.class, OtherSideClass.class, Latitude.class);
CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
- ComponentContainer container = new ComponentContainer();
+ ExtensionContainer container = mock(ExtensionContainer.class);
underTest.install(container, noExtensionFilter(), t -> t != Latitude.class);
- assertAddedExtensions(container, WestSideClass.class);
- assertPropertyDefinitions(container);
- }
-
- @Test
- @UseDataProvider("allMethodsToAddExtension")
- public void install_adds_PropertyDefinition_from_annotation_no_matter_annotations(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
- List<Object> extensions = ImmutableList.of(WestSidePropertyDefinition.class, EastSidePropertyDefinition.class,
- OtherSidePropertyDefinition.class, LatitudePropertyDefinition.class, BlankPropertyDefinition.class);
- CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
- when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
- ComponentContainer container = new ComponentContainer();
-
- underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
-
- assertAddedExtensions(container, WestSidePropertyDefinition.class, LatitudePropertyDefinition.class);
- assertPropertyDefinitions(container, "westKey", "eastKey", "otherKey", "latitudeKey", "blankKey");
- }
-
- @Test
- @UseDataProvider("allMethodsToAddExtension")
- public void install_adds_PropertyDefinition_from_annotation_no_matter_annotations_even_if_filtered_out(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
- List<Object> extensions = ImmutableList.of(WestSidePropertyDefinition.class, EastSidePropertyDefinition.class,
- OtherSidePropertyDefinition.class, LatitudePropertyDefinition.class, BlankPropertyDefinition.class);
- CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
- when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
- ComponentContainer container = new ComponentContainer();
-
- underTest.install(container, noExtensionFilter(), t -> false);
-
- assertAddedExtensions(container, 0);
- assertPropertyDefinitions(container, "westKey", "eastKey", "otherKey", "latitudeKey", "blankKey");
+ verify(container).addExtension(coreExtension.getName(), WestSideClass.class);
+ verify(container).declareExtension(coreExtension.getName(), OtherSideClass.class);
+ verify(container).declareExtension(coreExtension.getName(), EastSideClass.class);
+ verify(container).declareExtension(coreExtension.getName(), Latitude.class);
+ verifyNoMoreInteractions(container);
}
@Test
@@ -216,12 +188,13 @@ public class CoreExtensionsInstallerTest {
List<Object> extensions = ImmutableList.of(propertyDefinitionNoCategory, propertyDefinitionWithCategory);
CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
- ComponentContainer container = new ComponentContainer();
+ ExtensionContainer container = mock(ExtensionContainer.class);
underTest.install(container, noExtensionFilter(), noAdditionalSideFilter());
- assertAddedExtensions(container, 0);
- assertPropertyDefinitions(container, coreExtension, propertyDefinitionNoCategory, propertyDefinitionWithCategory);
+ verify(container).declareExtension(coreExtension.getName(), propertyDefinitionNoCategory);
+ verify(container).declareExtension(coreExtension.getName(), propertyDefinitionWithCategory);
+ verifyNoMoreInteractions(container);
}
@DataProvider
@@ -244,46 +217,10 @@ public class CoreExtensionsInstallerTest {
};
}
- private static void assertAddedExtensions(ComponentContainer container, int addedExtensions) {
- Collection<ComponentAdapter<?>> adapters = container.getPicoContainer().getComponentAdapters();
- assertThat(adapters)
- .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + addedExtensions);
- }
-
- private static void assertAddedExtensions(ComponentContainer container, Class... classes) {
- Collection<ComponentAdapter<?>> adapters = container.getPicoContainer().getComponentAdapters();
+ private static void assertAddedExtensions(ListContainer container, int addedExtensions) {
+ List<Object> adapters = container.getAddedObjects();
assertThat(adapters)
- .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + classes.length);
-
- Stream<Class> installedExtensions = adapters.stream()
- .map(t -> (Class) t.getComponentImplementation())
- .filter(t -> !PropertyDefinitions.class.isAssignableFrom(t) && t != ComponentContainer.class);
- assertThat(installedExtensions)
- .contains(classes)
- .hasSize(classes.length);
- }
-
- private void assertPropertyDefinitions(ComponentContainer container, String... keys) {
- PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
- if (keys.length == 0) {
- assertThat(propertyDefinitions.getAll()).isEmpty();
- } else {
- for (String key : keys) {
- assertThat(propertyDefinitions.get(key)).isNotNull();
- }
- }
- }
-
- private void assertPropertyDefinitions(ComponentContainer container, CoreExtension coreExtension, PropertyDefinition... definitions) {
- PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
- if (definitions.length == 0) {
- assertThat(propertyDefinitions.getAll()).isEmpty();
- } else {
- for (PropertyDefinition definition : definitions) {
- PropertyDefinition actual = propertyDefinitions.get(definition.key());
- assertThat(actual.category()).isEqualTo(definition.category() == null ? coreExtension.getName() : definition.category());
- }
- }
+ .hasSize(addedExtensions);
}
private static CoreExtension newCoreExtension() {
@@ -342,12 +279,6 @@ public class CoreExtensionsInstallerTest {
}
- @Property(key = "westKey", name = "westName")
- @WestSide
- public static class WestSidePropertyDefinition {
-
- }
-
@Property(key = "eastKey", name = "eastName")
@EastSide
public static class EastSidePropertyDefinition {
@@ -360,13 +291,6 @@ public class CoreExtensionsInstallerTest {
}
- @Property(key = "latitudeKey", name = "latitudeName")
- @WestSide
- @EastSide
- public static class LatitudePropertyDefinition {
-
- }
-
@Property(key = "blankKey", name = "blankName")
public static class BlankPropertyDefinition {
diff --git a/sonar-core/src/test/java/org/sonar/core/language/LanguagesProviderTest.java b/sonar-core/src/test/java/org/sonar/core/language/LanguagesProviderTest.java
new file mode 100644
index 00000000000..8bb931fbc98
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/language/LanguagesProviderTest.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.language;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.Test;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public class LanguagesProviderTest {
+
+ @Test
+ public void should_provide_default_instance_when_no_language() {
+ LanguagesProvider provider = new LanguagesProvider();
+ Languages languages = provider.provide(Optional.empty());
+
+ assertThat(languages).isNotNull();
+ assertThat(languages.all()).isEmpty();
+ }
+
+ @Test
+ public void should_provide_instance_when_languages() {
+ Language A = mock(Language.class);
+ when(A.getKey()).thenReturn("a");
+ Language B = mock(Language.class);
+ when(B.getKey()).thenReturn("b");
+
+ LanguagesProvider provider = new LanguagesProvider();
+ List<Language> languageList = Arrays.asList(A, B);
+ Languages languages = provider.provide(Optional.of(languageList));
+
+ assertThat(languages).isNotNull();
+ assertThat(languages.all())
+ .hasSize(2)
+ .contains(A, B);
+ }
+
+}
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
deleted file mode 100644
index 4351938fcb0..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Test;
-import org.picocontainer.Startable;
-import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.Property;
-import org.sonar.api.config.PropertyDefinitions;
-
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-public class ComponentContainerTest {
-
-
- @Test
- public void shouldRegisterItself() {
- ComponentContainer container = new ComponentContainer();
- assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container);
- }
-
- @Test
- 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).started).isTrue();
- assertThat(container.getComponentByType(StartableStoppableComponent.class).stopped).isFalse();
- verify(container).doBeforeStart();
- verify(container).doAfterStart();
-
- container.stopComponents();
- 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();
- final StartableStoppableComponent childComponent = new StartableStoppableComponent();
- ComponentContainer parentContainer = new ComponentContainer() {
- @Override
- public void doAfterStart() {
- ComponentContainer childContainer = new ComponentContainer(this);
- childContainer.add(childComponent);
- childContainer.execute();
- }
- };
- parentContainer.add(parentComponent);
- parentContainer.execute();
- assertThat(parentComponent.started).isTrue();
- assertThat(parentComponent.stopped).isTrue();
- assertThat(childComponent.started).isTrue();
- assertThat(childComponent.stopped).isTrue();
- }
-
- @Test
- public void should_stop_hierarchy_of_containers_on_failure() {
- StartableStoppableComponent parentComponent = new StartableStoppableComponent();
- final StartableStoppableComponent childComponent1 = new StartableStoppableComponent();
- final UnstartableComponent childComponent2 = new UnstartableComponent();
- ComponentContainer parentContainer = new ComponentContainer() {
- @Override
- public void doAfterStart() {
- ComponentContainer childContainer = new ComponentContainer(this);
- childContainer.add(childComponent1);
- childContainer.add(childComponent2);
- childContainer.execute();
- }
- };
- parentContainer.add(parentComponent);
- try {
- parentContainer.execute();
- fail();
- } catch (Exception e) {
- assertThat(parentComponent.started).isTrue();
- assertThat(parentComponent.stopped).isTrue();
- assertThat(childComponent1.started).isTrue();
- assertThat(childComponent1.stopped).isTrue();
- }
- }
-
- @Test
- public void testChild() {
- ComponentContainer parent = new ComponentContainer();
- parent.startComponents();
-
- ComponentContainer child = parent.createChild();
- child.addSingleton(StartableStoppableComponent.class);
- child.startComponents();
-
- assertThat(child.getParent()).isSameAs(parent);
- assertThat(parent.getChildren()).containsOnly(child);
- assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child);
- assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent);
- assertThat(child.getComponentByType(StartableStoppableComponent.class)).isNotNull();
- assertThat(parent.getComponentByType(StartableStoppableComponent.class)).isNull();
-
- parent.stopComponents();
- }
-
- @Test
- public void testRemoveChild() {
- ComponentContainer parent = new ComponentContainer();
- parent.startComponents();
-
- ComponentContainer child = parent.createChild();
- assertThat(parent.getChildren()).containsOnly(child);
-
- parent.removeChild(child);
- assertThat(parent.getChildren()).isEmpty();
- }
-
- @Test
- public void support_multiple_children() {
- ComponentContainer parent = new ComponentContainer();
- parent.startComponents();
- ComponentContainer child1 = parent.createChild();
- child1.startComponents();
- ComponentContainer child2 = parent.createChild();
- child2.startComponents();
- assertThat(parent.getChildren()).containsOnly(child1, child2);
-
- child1.stopComponents();
- assertThat(parent.getChildren()).containsOnly(child2);
-
- parent.stopComponents();
- assertThat(parent.getChildren()).isEmpty();
- }
-
- @Test
- public void shouldForwardStartAndStopToDescendants() {
- ComponentContainer grandParent = new ComponentContainer();
- ComponentContainer parent = grandParent.createChild();
- ComponentContainer child = parent.createChild();
- child.addSingleton(StartableStoppableComponent.class);
-
- grandParent.startComponents();
-
- StartableStoppableComponent component = child.getComponentByType(StartableStoppableComponent.class);
- assertTrue(component.started);
-
- parent.stopComponents();
- assertTrue(component.stopped);
- }
-
- @Test
- public void shouldDeclareComponentProperties() {
- ComponentContainer container = new ComponentContainer();
- container.addSingleton(ComponentWithProperty.class);
-
- PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
- assertThat(propertyDefinitions.get("foo")).isNotNull();
- assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar");
- }
-
- @Test
- public void shouldDeclareExtensionWithoutAddingIt() {
- ComponentContainer container = new ComponentContainer();
- PluginInfo plugin = mock(PluginInfo.class);
- container.declareExtension(plugin, ComponentWithProperty.class);
-
- PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
- assertThat(propertyDefinitions.get("foo")).isNotNull();
- assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull();
- }
-
- @Test
- public void shouldDeclareExtensionWhenAdding() {
- ComponentContainer container = new ComponentContainer();
- PluginInfo plugin = mock(PluginInfo.class);
- container.addExtension(plugin, ComponentWithProperty.class);
-
- PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
- assertThat(propertyDefinitions.get("foo")).isNotNull();
- assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
- assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull();
- }
-
- @Test
- public void test_add_class() {
- ComponentContainer container = new ComponentContainer();
- container.add(ComponentWithProperty.class, SimpleComponent.class);
- assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
- assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
- }
-
- @Test
- public void test_add_collection() {
- ComponentContainer container = new ComponentContainer();
- container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class));
- assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
- assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
- }
-
- @Test
- public void test_add_adapter() {
- ComponentContainer container = new ComponentContainer();
- container.add(new SimpleComponentProvider());
- assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
- }
-
- @Test
- public void should_sanitize_pico_exception_on_start_failure() {
- ComponentContainer container = new ComponentContainer();
- container.add(UnstartableComponent.class);
-
- // do not expect a PicoException
- assertThatThrownBy(container::startComponents)
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void display_plugin_name_when_failing_to_add_extension() {
- ComponentContainer container = new ComponentContainer();
- PluginInfo plugin = mock(PluginInfo.class);
-
- container.startComponents();
-
- assertThatThrownBy(() -> container.addExtension(plugin, UnstartableComponent.class))
- .isInstanceOf(IllegalStateException.class)
- .hasMessageContaining("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent");
- }
-
- @Test
- public void test_start_failure() {
- ComponentContainer container = new ComponentContainer();
- StartableStoppableComponent startable = new StartableStoppableComponent();
- container.add(startable, UnstartableComponent.class);
-
- try {
- container.execute();
- fail();
- } catch (Exception e) {
- assertThat(startable.started).isTrue();
-
- // container stops the components that have already been started
- assertThat(startable.stopped).isTrue();
- }
- }
-
- @Test
- public void stop_container_does_not_fail_and_all_stoppable_components_are_stopped_even_if_one_or_more_stop_method_call_fail() {
- ComponentContainer container = new ComponentContainer();
- container.add(FailingStopWithISEComponent.class, FailingStopWithISEComponent2.class, FailingStopWithErrorComponent.class, FailingStopWithErrorComponent2.class);
- container.startComponents();
- StartableStoppableComponent[] components = {
- container.getComponentByType(FailingStopWithISEComponent.class),
- container.getComponentByType(FailingStopWithISEComponent2.class),
- container.getComponentByType(FailingStopWithErrorComponent.class),
- container.getComponentByType(FailingStopWithErrorComponent2.class)
- };
-
- container.stopComponents();
-
- Arrays.stream(components).forEach(startableComponent -> assertThat(startableComponent.stopped).isTrue());
- }
-
- @Test
- public void stop_container_stops_all_stoppable_components_even_in_case_of_OOM_in_any_stop_method() {
- ComponentContainer container = new ComponentContainer();
- container.add(FailingStopWithOOMComponent.class, FailingStopWithOOMComponent2.class);
- container.startComponents();
- StartableStoppableComponent[] components = {
- container.getComponentByType(FailingStopWithOOMComponent.class),
- container.getComponentByType(FailingStopWithOOMComponent2.class)
- };
-
- container.stopComponents();
-
- assertThat(container.getPicoContainer().getLifecycleState().isDisposed()).isTrue();
- Arrays.stream(components).forEach(cpt -> assertThat(cpt.stopped).isTrue());
- }
-
- @Test
- public void stop_exception_should_not_hide_start_exception() {
- ComponentContainer container = new ComponentContainer();
- container.add(UnstartableComponent.class, FailingStopWithISEComponent.class);
-
- assertThatThrownBy(container::execute)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Fail to start");
- }
-
- @Test
- public void stop_exceptionin_API_component_should_not_hide_start_exception() {
- ComponentContainer container = new ComponentContainer();
- container.add(UnstartableApiComponent.class, FailingStopWithISEComponent.class);
-
- assertThatThrownBy(container::execute)
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Fail to start");
- }
-
- @Test
- public void should_execute_components() {
- ComponentContainer container = new ComponentContainer();
- StartableStoppableComponent component = new StartableStoppableComponent();
- container.add(component);
-
- container.execute();
-
- assertThat(component.started).isTrue();
- assertThat(component.stopped).isTrue();
- }
-
- /**
- * Method close() must be called even if the methods start() or stop()
- * are not defined.
- */
- @Test
- public void should_close_components_without_lifecycle() {
- ComponentContainer container = new ComponentContainer();
- CloseableComponent component = new CloseableComponent();
- container.add(component);
-
- container.execute();
-
- assertThat(component.isClosed).isTrue();
- }
-
- /**
- * Method close() must be executed after stop()
- */
- @Test
- public void should_close_Closeable_components_with_lifecycle() {
- ComponentContainer container = new ComponentContainer();
- StartableCloseableComponent component = new StartableCloseableComponent();
- container.add(component);
-
- container.execute();
-
- assertThat(component.isStopped).isTrue();
- assertThat(component.isClosed).isTrue();
- assertThat(component.isClosedAfterStop).isTrue();
- }
-
- /**
- * 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 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 static class FailingStopWithISEComponent extends StartableStoppableComponent {
- public void stop() {
- super.stop();
- throw new IllegalStateException("Faking IllegalStateException thrown by stop method of " + getClass().getSimpleName());
- }
- }
-
- public static class FailingStopWithISEComponent2 extends FailingStopWithErrorComponent {
- }
-
- public static class FailingStopWithErrorComponent extends StartableStoppableComponent {
- public void stop() {
- super.stop();
- throw new Error("Faking Error thrown by stop method of " + getClass().getSimpleName());
- }
- }
-
- public static class FailingStopWithErrorComponent2 extends FailingStopWithErrorComponent {
- }
-
- public static class FailingStopWithOOMComponent extends StartableStoppableComponent {
- public void stop() {
- super.stop();
- consumeAvailableMemory();
- }
-
- private static List<Object> consumeAvailableMemory() {
- List<Object> holder = new ArrayList<>();
- while (true) {
- holder.add(new byte[128 * 1024]);
- }
- }
- }
-
- public static class FailingStopWithOOMComponent2 extends FailingStopWithOOMComponent {
-
- }
-
- @Property(key = "foo", defaultValue = "bar", name = "Foo")
- public static class ComponentWithProperty {
-
- }
-
- public static class SimpleComponent {
-
- }
-
- public static class SimpleComponentProvider extends ProviderAdapter {
- public SimpleComponent provide() {
- return new SimpleComponent();
- }
- }
-
- public static class CloseableComponent implements AutoCloseable {
- public boolean isClosed = false;
-
- @Override
- public void close() {
- isClosed = true;
- }
- }
-
- 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() {
- isClosed = true;
- isClosedAfterStop = isStopped;
- }
- }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/LazyUnlessStartableStrategyTest.java b/sonar-core/src/test/java/org/sonar/core/platform/LazyUnlessStartableStrategyTest.java
new file mode 100644
index 00000000000..8279e94c935
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/LazyUnlessStartableStrategyTest.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.junit.Test;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LazyUnlessStartableStrategyTest {
+ private final LazyUnlessStartableStrategy postProcessor = new LazyUnlessStartableStrategy();
+
+ @Test
+ public void sets_all_beans_lazy() {
+ DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+ beanFactory.registerBeanDefinition("bean1", new RootBeanDefinition());
+ assertThat(beanFactory.getBeanDefinition("bean1").isLazyInit()).isFalse();
+
+ postProcessor.postProcessBeanFactory(beanFactory);
+ assertThat(beanFactory.getBeanDefinition("bean1").isLazyInit()).isTrue();
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ListContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ListContainerTest.java
new file mode 100644
index 00000000000..00741c8d717
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/ListContainerTest.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.util.Arrays;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+public class ListContainerTest {
+
+ @Test
+ public void register_beans() {
+ ListContainer container = new ListContainer();
+ container.add(
+ A.class,
+ new VirtualModule(),
+ Arrays.asList(C.class, D.class)
+ );
+ assertThat(container.getAddedObjects()).contains(A.class, B.class, C.class, D.class);
+ }
+
+ @Test
+ public void addExtension_register_beans() {
+ ListContainer container = new ListContainer();
+ container
+ .addExtension("A", A.class)
+ .addExtension("B", B.class);
+ assertThat(container.getAddedObjects()).contains(A.class, B.class);
+ }
+
+ @Test
+ public void declareExtension_does_nothing() {
+ ListContainer container = new ListContainer();
+ assertThatNoException().isThrownBy(() -> container
+ .declareExtension("A", A.class)
+ .declareExtension(mock(PluginInfo.class), B.class));
+ }
+
+ @Test
+ public void unsupported_method_should_throw_exception() {
+ ListContainer container = new ListContainer();
+ container.add(A.class);
+ assertThatThrownBy(() -> container.getComponentByType(A.class)).isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(() -> container.getOptionalComponentByType(A.class)).isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(() -> container.getComponentsByType(A.class)).isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(container::getParent).isInstanceOf(UnsupportedOperationException.class);
+ }
+
+ class A {
+ }
+
+ class B {
+ }
+
+ class C {
+ }
+
+ class D {
+ }
+
+ class VirtualModule extends Module {
+
+ @Override protected void configureModule() {
+ add(B.class);
+ }
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ModuleTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ModuleTest.java
index df50374a0cd..9a2bdaf2426 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/ModuleTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/ModuleTest.java
@@ -24,8 +24,7 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ModuleTest {
- ComponentContainer container = new ComponentContainer();
- int initialSize = sizeOf(container);
+ private final ListContainer container = new ListContainer();
@Test(expected = NullPointerException.class)
public void configure_throws_NPE_if_container_is_empty() {
@@ -46,7 +45,7 @@ public class ModuleTest {
}
}.configure(container);
- assertThat(sizeOf(container)).isSameAs(initialSize);
+ assertThat(container.getAddedObjects()).isEmpty();
}
@Test
@@ -58,7 +57,7 @@ public class ModuleTest {
}
}.configure(container);
- assertThat(sizeOf(container)).isEqualTo(initialSize);
+ assertThat(container.getAddedObjects()).isEmpty();
}
@Test
@@ -70,10 +69,10 @@ public class ModuleTest {
}
}.configure(container);
- assertThat(sizeOf(container)).isEqualTo(initialSize + 2);
+ assertThat(container.getAddedObjects()).hasSize(2);
}
- private static int sizeOf(ComponentContainer container) {
- return container.getPicoContainer().getComponentAdapters().size();
+ private static int sizeOf(SpringComponentContainer container) {
+ return container.context.getBeanDefinitionCount();
}
}
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
deleted file mode 100644
index e21dd7a3108..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.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() 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");
- }
- }
-
- @Test
- public void shouldSanitizePicoLifecycleException_no_wrapper_message() {
- Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg")));
-
- assertThat(th).isInstanceOf(IllegalStateException.class);
- assertThat(th.getMessage()).isEqualTo("msg");
- }
-
- @Test
- public void shouldNotSanitizeOtherExceptions() {
- Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo"));
-
- assertThat(th).isInstanceOf(IllegalArgumentException.class);
- assertThat(th.getMessage()).isEqualTo("foo");
- }
-
- @Test
- public void shouldPropagateInitialUncheckedException() {
- try {
- PicoUtils.propagate(newPicoLifecycleException());
- fail();
- } catch (RuntimeException e) {
- assertThat(e).isInstanceOf(IllegalStateException.class);
- }
- }
-
-
- private PicoLifecycleException newPicoLifecycleException() {
- MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE);
- container.addComponent(UncheckedFailureComponent.class);
- try {
- container.start();
- throw new IllegalStateException("An exception should have been thrown by start()");
-
- } catch (PicoLifecycleException e) {
- return e;
- }
- }
-
- public static class UncheckedFailureComponent implements Startable {
- public void start() {
- throw new IllegalStateException("A good reason to fail");
- }
-
- @Override
- public void stop() {
- // nothing to do
- }
- }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PriorityBeanFactoryTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PriorityBeanFactoryTest.java
new file mode 100644
index 00000000000..70f7fd89c9d
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PriorityBeanFactoryTest.java
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 javax.annotation.Priority;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
+import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class PriorityBeanFactoryTest {
+ private final DefaultListableBeanFactory parentBeanFactory = new PriorityBeanFactory();
+ private final DefaultListableBeanFactory beanFactory = new PriorityBeanFactory();
+
+ @Before
+ public void setUp() {
+ // needed to support autowiring with @Inject
+ beanFactory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
+ //needed to read @Priority
+ beanFactory.setDependencyComparator(new AnnotationAwareOrderComparator());
+ beanFactory.setParentBeanFactory(parentBeanFactory);
+ }
+
+ @Test
+ public void give_priority_to_child_container() {
+ parentBeanFactory.registerBeanDefinition("A1", new RootBeanDefinition(A1.class));
+
+ beanFactory.registerBeanDefinition("A2", new RootBeanDefinition(A2.class));
+ beanFactory.registerBeanDefinition("B", new RootBeanDefinition(B.class));
+
+ assertThat(beanFactory.getBean(B.class).dep.getClass()).isEqualTo(A2.class);
+ }
+
+ @Test
+ public void follow_priority_annotations() {
+ parentBeanFactory.registerBeanDefinition("A3", new RootBeanDefinition(A3.class));
+
+ beanFactory.registerBeanDefinition("A1", new RootBeanDefinition(A1.class));
+ beanFactory.registerBeanDefinition("A2", new RootBeanDefinition(A2.class));
+ beanFactory.registerBeanDefinition("B", new RootBeanDefinition(B.class));
+
+ assertThat(beanFactory.getBean(B.class).dep.getClass()).isEqualTo(A3.class);
+ }
+
+ @Test
+ public void throw_NoUniqueBeanDefinitionException_if_cant_find_single_bean_with_higher_priority() {
+ beanFactory.registerBeanDefinition("A1", new RootBeanDefinition(A1.class));
+ beanFactory.registerBeanDefinition("A2", new RootBeanDefinition(A2.class));
+ beanFactory.registerBeanDefinition("B", new RootBeanDefinition(B.class));
+
+ assertThatThrownBy(() -> beanFactory.getBean(B.class))
+ .hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
+ }
+
+ private static class B {
+ private final A dep;
+
+ public B(A dep) {
+ this.dep = dep;
+ }
+ }
+
+ private interface A {
+
+ }
+
+ private static class A1 implements A {
+
+ }
+
+ private static class A2 implements A {
+
+ }
+
+ @Priority(1)
+ private static class A3 implements A {
+
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/SpringComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/SpringComponentContainerTest.java
new file mode 100644
index 00000000000..64be7c32987
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/SpringComponentContainerTest.java
@@ -0,0 +1,358 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.util.Arrays;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import org.junit.Test;
+import org.sonar.api.Property;
+import org.sonar.api.Startable;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyDefinitions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertThrows;
+
+public class SpringComponentContainerTest {
+ @Test
+ public void should_stop_after_failing() {
+ ApiStartable startStop = new ApiStartable();
+ SpringComponentContainer container = new SpringComponentContainer() {
+ @Override
+ public void doBeforeStart() {
+ add(startStop);
+ }
+
+ @Override
+ public void doAfterStart() {
+ getComponentByType(ApiStartable.class);
+ throw new IllegalStateException("doBeforeStart");
+ }
+ };
+
+ assertThrows("doBeforeStart", IllegalStateException.class, container::execute);
+ assertThat(startStop.start).isOne();
+ assertThat(startStop.stop).isOne();
+ }
+
+ @Test
+ public void add_registers_instance_with_toString() {
+ SpringComponentContainer container = new SimpleContainer(new ToString("a"), new ToString("b"));
+ container.startComponents();
+ assertThat(container.context.getBeanDefinitionNames())
+ .contains(
+ this.getClass().getClassLoader() + "-org.sonar.core.platform.SpringComponentContainerTest.ToString-a",
+ this.getClass().getClassLoader() + "-org.sonar.core.platform.SpringComponentContainerTest.ToString-b");
+ assertThat(container.getComponentsByType(ToString.class)).hasSize(2);
+ }
+
+ @Test
+ public void add_registers_class_with_classloader_and_fqcn() {
+ SpringComponentContainer container = new SimpleContainer(A.class, B.class);
+ container.startComponents();
+ assertThat(container.context.getBeanDefinitionNames())
+ .contains(
+ this.getClass().getClassLoader() + "-org.sonar.core.platform.SpringComponentContainerTest.A",
+ this.getClass().getClassLoader() + "-org.sonar.core.platform.SpringComponentContainerTest.B");
+ assertThat(container.getComponentByType(A.class)).isNotNull();
+ assertThat(container.getComponentByType(B.class)).isNotNull();
+ }
+
+ @Test
+ public void add_configures_module_instances() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.add(new TestModule());
+ container.startComponents();
+ assertThat(container.getComponentByType(A.class)).isNotNull();
+ }
+
+ @Test
+ public void get_optional_component_by_type_should_return_correctly() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.add(A.class);
+ container.startComponents();
+ assertThat(container.getOptionalComponentByType(A.class)).containsInstanceOf(A.class);
+ assertThat(container.getOptionalComponentByType(B.class)).isEmpty();
+ }
+
+ @Test
+ public void createChild_method_should_spawn_a_child_container(){
+ SpringComponentContainer parent = new SpringComponentContainer();
+ SpringComponentContainer child = parent.createChild();
+ assertThat(child).isNotEqualTo(parent);
+ assertThat(child.parent).isEqualTo(parent);
+ assertThat(parent.children).contains(child);
+ }
+
+ @Test
+ public void get_component_by_type_should_throw_exception_when_type_does_not_exist() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.add(A.class);
+ container.startComponents();
+ assertThatThrownBy(() -> container.getComponentByType(B.class))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Unable to load component class org.sonar.core.platform.SpringComponentContainerTest$B");
+ }
+
+ @Test
+ public void add_fails_if_adding_module_class() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.startComponents();
+ assertThatThrownBy(() -> container.add(TestModule.class))
+ .hasMessage("Modules should be added as instances")
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void should_throw_start_exception_if_stop_also_throws_exception() {
+ ErrorStopClass errorStopClass = new ErrorStopClass();
+ SpringComponentContainer container = new SpringComponentContainer() {
+ @Override
+ public void doBeforeStart() {
+ add(errorStopClass);
+ }
+
+ @Override
+ public void doAfterStart() {
+ getComponentByType(ErrorStopClass.class);
+ throw new IllegalStateException("doBeforeStart");
+ }
+ };
+ assertThrows("doBeforeStart", IllegalStateException.class, container::execute);
+ assertThat(errorStopClass.stopped).isTrue();
+ }
+
+ @Test
+ public void addExtension_supports_extensions_without_annotations() {
+ SpringComponentContainer container = new SimpleContainer(A.class, B.class);
+ container.addExtension("", ExtensionWithMultipleConstructorsAndNoAnnotations.class);
+ container.startComponents();
+ assertThat(container.getComponentByType(ExtensionWithMultipleConstructorsAndNoAnnotations.class).gotBothArgs).isTrue();
+ }
+
+ @Test
+ public void addExtension_supports_extension_instances_without_annotations() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.addExtension("", new ExtensionWithMultipleConstructorsAndNoAnnotations(new A()));
+ container.startComponents();
+ assertThat(container.getComponentByType(ExtensionWithMultipleConstructorsAndNoAnnotations.class)).isNotNull();
+ }
+
+ @Test
+ public void addExtension_resolves_iterables() {
+ List<Class<?>> classes = Arrays.asList(A.class, B.class);
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.addExtension("", classes);
+ container.startComponents();
+ assertThat(container.getComponentByType(A.class)).isNotNull();
+ assertThat(container.getComponentByType(B.class)).isNotNull();
+ }
+
+ @Test
+ public void addExtension_adds_property_with_PluginInfo() {
+ PluginInfo info = new PluginInfo("plugin1").setName("plugin1");
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.addExtension(info, A.class);
+
+ container.startComponents();
+ PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+ PropertyDefinition propertyDefinition = propertyDefinitions.get("k");
+ assertThat(propertyDefinition.key()).isEqualTo("k");
+ assertThat(propertyDefinitions.getCategory("k")).isEqualTo("plugin1");
+ }
+
+ @Test
+ public void declareExtension_adds_property() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.addExtension((PluginInfo) null, A.class);
+
+ container.startComponents();
+ PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+ PropertyDefinition propertyDefinition = propertyDefinitions.get("k");
+ assertThat(propertyDefinition.key()).isEqualTo("k");
+ assertThat(propertyDefinitions.getCategory("k")).isEmpty();
+ }
+
+ @Test
+ public void stop_should_stop_children() {
+ SpringComponentContainer parent = new SpringComponentContainer();
+ ApiStartable s1 = new ApiStartable();
+ parent.add(s1);
+ parent.startComponents();
+ SpringComponentContainer child = new SpringComponentContainer(parent);
+ assertThat(child.getParent()).isEqualTo(parent);
+ assertThat(parent.children).containsOnly(child);
+ ApiStartable s2 = new ApiStartable();
+ child.add(s2);
+ child.startComponents();
+
+ parent.stopComponents();
+ assertThat(s1.stop).isOne();
+ assertThat(s2.stop).isOne();
+ }
+
+ @Test
+ public void stop_should_remove_container_from_parent() {
+ SpringComponentContainer parent = new SpringComponentContainer();
+ SpringComponentContainer child = new SpringComponentContainer(parent);
+ assertThat(parent.children).containsOnly(child);
+ child.stopComponents();
+ assertThat(parent.children).isEmpty();
+ }
+
+ @Test
+ public void bean_create_fails_if_class_has_default_constructor_and_other_constructors() {
+ SpringComponentContainer container = new SpringComponentContainer();
+ container.add(ClassWithMultipleConstructorsIncNoArg.class);
+ container.startComponents();
+ assertThatThrownBy(() -> container.getComponentByType(ClassWithMultipleConstructorsIncNoArg.class))
+ .hasRootCauseMessage("Constructor annotations missing in: class org.sonar.core.platform.SpringComponentContainerTest$ClassWithMultipleConstructorsIncNoArg");
+ }
+
+ @Test
+ public void support_start_stop_callbacks() {
+ JsrLifecycleCallbacks jsr = new JsrLifecycleCallbacks();
+ ApiStartable api = new ApiStartable();
+ AutoClose closeable = new AutoClose();
+
+ SpringComponentContainer container = new SimpleContainer(jsr, api, closeable) {
+ @Override
+ public void doAfterStart() {
+ // force lazy instantiation
+ getComponentByType(JsrLifecycleCallbacks.class);
+ getComponentByType(ApiStartable.class);
+ getComponentByType(AutoClose.class);
+ }
+ };
+ container.execute();
+
+ assertThat(closeable.closed).isOne();
+ assertThat(jsr.postConstruct).isOne();
+ assertThat(jsr.preDestroy).isOne();
+ assertThat(api.start).isOne();
+ assertThat(api.stop).isOne();
+ }
+
+ private static class TestModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(A.class);
+ }
+ }
+
+ private static class JsrLifecycleCallbacks {
+ private int postConstruct = 0;
+ private int preDestroy = 0;
+
+ @PostConstruct
+ public void postConstruct() {
+ postConstruct++;
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ preDestroy++;
+ }
+ }
+
+ private static class AutoClose implements AutoCloseable {
+ private int closed = 0;
+
+ @Override
+ public void close() {
+ closed++;
+ }
+ }
+
+ private static class ApiStartable implements Startable {
+ private int start = 0;
+ private int stop = 0;
+
+ public void start() {
+ start++;
+ }
+
+ public void stop() {
+ stop++;
+ }
+ }
+
+ private static class ToString {
+ private final String toString;
+
+ public ToString(String toString) {
+ this.toString = toString;
+ }
+
+ @Override
+ public String toString() {
+ return toString;
+ }
+ }
+
+ @Property(key = "k", name = "name")
+ private static class A {
+ }
+
+ private static class B {
+ }
+
+ private static class ClassWithMultipleConstructorsIncNoArg {
+ public ClassWithMultipleConstructorsIncNoArg() {
+ }
+
+ public ClassWithMultipleConstructorsIncNoArg(A a) {
+ }
+ }
+
+ private static class ExtensionWithMultipleConstructorsAndNoAnnotations {
+ private boolean gotBothArgs = false;
+
+ public ExtensionWithMultipleConstructorsAndNoAnnotations(A a) {
+ }
+
+ public ExtensionWithMultipleConstructorsAndNoAnnotations(A a, B b) {
+ gotBothArgs = true;
+ }
+ }
+
+ private static class ErrorStopClass implements Startable {
+ private boolean stopped = false;
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ stopped = true;
+ throw new IllegalStateException("stop");
+ }
+ }
+
+ private static class SimpleContainer extends SpringComponentContainer {
+ public SimpleContainer(Object... objects) {
+ add(objects);
+ }
+ }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/StartableBeanPostProcessorTest.java b/sonar-core/src/test/java/org/sonar/core/platform/StartableBeanPostProcessorTest.java
new file mode 100644
index 00000000000..2103fcad0da
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/platform/StartableBeanPostProcessorTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.junit.Test;
+import org.sonar.api.Startable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class StartableBeanPostProcessorTest {
+ private final StartableBeanPostProcessor underTest = new StartableBeanPostProcessor();
+
+ @Test
+ public void starts_api_startable() {
+ Startable startable = mock(Startable.class);
+ underTest.postProcessBeforeInitialization(startable, "startable");
+ verify(startable).start();
+ verifyNoMoreInteractions(startable);
+ }
+
+ @Test
+ public void stops_api_startable() {
+ Startable startable = mock(Startable.class);
+ underTest.postProcessBeforeDestruction(startable, "startable");
+ verify(startable).stop();
+ verifyNoMoreInteractions(startable);
+ }
+
+ @Test
+ public void startable_and_autoCloseable_should_require_destruction(){
+ assertThat(underTest.requiresDestruction(mock(Startable.class))).isTrue();
+ assertThat(underTest.requiresDestruction(mock(org.sonar.api.Startable.class))).isTrue();
+ assertThat(underTest.requiresDestruction(mock(Object.class))).isFalse();
+ }
+}
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
deleted file mode 100644
index 3f8b427800c..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/platform/StartableCloseableSafeLifecyleStrategyTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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");
- }
-}