diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2022-02-01 15:16:25 -0600 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-02-22 20:02:46 +0000 |
commit | 60c1a4038e041a342dda9810e6fd761d66b01bdb (patch) | |
tree | 0e76b4252e4d7d257cf4ddcb6f081996bb1e03ab /sonar-core/src | |
parent | 9694d4113bf401b84e86e0223dbea8f5339388d8 (diff) | |
download | sonarqube-60c1a4038e041a342dda9810e6fd761d66b01bdb.tar.gz sonarqube-60c1a4038e041a342dda9810e6fd761d66b01bdb.zip |
SONAR-15994 Migrate Sonarqube IOC framework from Pico to Spring
Diffstat (limited to 'sonar-core/src')
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"); - } -} |