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/main | |
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/main')
17 files changed, 759 insertions, 477 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; |