Skip to content

chore: import vaadin-cdi as a Flow module and run its tests in validation - #25525

Merged
Artur- merged 644 commits into
mainfrom
chore/import-cdi-to-flow
Sep 14, 2026
Merged

Artur- merged 644 commits into
mainfrom
chore/import-cdi-to-flow

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The vaadin-cdi integration used to live in its own repository. This moves it into Flow as a normal module, with its full git history, so it builds and releases together with Flow and shares Flow's version number. Its Arquillian test suite moves to flow-tests/vaadin-cdi-tests and now runs in CI.

What changed

Behavior change (only for apps using com.vaadin:vaadin-cdi): two things move for them, even though no Java signature changed.

  • The artifact keeps its GAV but now takes Flow's version number. It previously had its own scheme, up to 16.1.2.
  • RouteScopedContext now reports a missing route scope with IllegalStateException (naming the owner and the bean) where it used to fail with a bare NullPointerException. This happens when a bean that names its owner with @RouteScopeOwner is resolved before any navigation on the UI.

For everyone else this is additive. No existing Flow source file is touched. The changes outside the two new modules are build and CI wiring only.

Imported as-is:

  • vaadin-cdi/ — 36 main sources in com.vaadin.cdi, .annotation, .context and .util, plus 65 unit tests in 11 classes (89 executions, because one abstract base class runs against six different scope bindings). Registered in the root pom.xml, in flow-bom, and in the module tables in CLAUDE.md and guidelines/repository.md.
  • flow-tests/vaadin-cdi-tests/ — the Arquillian suite (17 test classes, 58 tests) plus its test application. Added to flow-tests/pom.xml.

Small fixes to the imported code, all behavior-preserving except the one called out above:

  • CdiVaadinServlet clears its servlet-name ThreadLocal with remove() instead of set(null), so no entry stays attached to a pooled container thread. getCurrentServletName() still answers null.
  • RouteScopedContext.navigationChainHasOwner checks for missing navigation data instead of dereferencing it.
  • BeanManagerProvider holds its ClassLoader map in a final field (the map is already concurrent, and the field is never reassigned) and drops a dead recursion branch in getParentBeanManagerInfo.
  • ClassUtils.extractPossiblyGenericMethod tolerates the null class that its own extractMethod already documents as allowed.
  • DependentProvider keeps writing its CreationalContext. The declared type is not Serializable, but the instances containers supply are, and dropping the write would leave a deserialized provider unable to destroy its @Dependent instance. Sonar's java:S2118 is ignored for that one file.

Build and CI:

  • New cdi-tests job in validation.yml, one leg per container: tomee, wildfly, payara, liberty, tomcat-weld. tomee runs on every change as a smoke test; the other four only when the CDI sources or the root pom.xml change (a weld.version bump there reaches the tomcat-weld profile). cleanup-artifacts now depends on the job and fails the build if it fails.
  • computeMatrix.js excludes flow-tests/vaadin-cdi-tests from the generic IT matrix, because the tests need a real application server.
  • update-since-tags.yml gives vaadin-cdi its own @since index directory, the same way vaadin-spring has one. Its 46 old releases would otherwise land in the gap between Flow 9.2 and 23.0 and rewrite @since tags across most of the framework.
  • Root pom.xml adds managed weld.version / weld.junit.version and the weld-se-core and weld-junit5 entries the module's unit tests need.
  • In the test module's POM: build-helper:parse-version is switched off, because it sets property names ending in ?, which help:effective-pom then writes as illegal XML element names that the strict POM reader ShrinkWrap uses rejects. Nothing in the module reads ${parsedVersion.*}. A replaceregexp still strips the duplicate XML declaration, and xmlvalidate fails the build if the output changes shape again. The Open Liberty Arquillian adapter is scoped to test.
  • Coverage measurement skips src/main/java/com/vaadin/cdi/util/**. That package is DeltaSpike-derived plumbing that only the container suite exercises, and Sonar is fed unit-test coverage only, so it reads as untested no matter how much of it runs.

Running the suite locally needs JDK 21, the version validation uses. On JDK 24 and later java.security.Policy.setPolicy throws, and TomEE calls it while installing its JACC policy provider, so the container never starts. Both READMEs say so.

API Changes

All new to this repository. The code is carried over from the vaadin-cdi repository unchanged, so nothing here is new to an application that already depends on com.vaadin:vaadin-cdi. No signature was changed or removed by the import. 37 public types added: 9 in com.vaadin.cdi, 9 in com.vaadin.cdi.annotation, 9 in com.vaadin.cdi.context, 10 in com.vaadin.cdi.util. Nothing is deprecated.

com.vaadin.cdi.AbstractCdiInstantiator

// Added
abstract public class AbstractCdiInstantiator implements Instantiator
protected abstract DefaultInstantiator getDelegate()
public abstract BeanManager getBeanManager()
public <T> T getOrCreate(Class<T> type)
public I18NProvider getI18NProvider()
public MenuAccessControl getMenuAccessControl()
public PageTitleGenerator getPageTitleGenerator()
public Stream<VaadinServiceInitListener> getServiceInitListeners()

com.vaadin.cdi.CdiInstantiator

// Added
public class CdiInstantiator extends AbstractCdiInstantiator
public CdiInstantiator(BeanManager beanManager, VaadinService service)
protected DefaultInstantiator getDelegate()
public BeanManager getBeanManager()
public <T extends Component> T createComponent(Class<T> componentClass)

com.vaadin.cdi.CdiInstantiatorFactory

// Added
@VaadinServiceScoped @VaadinServiceEnabled
public class CdiInstantiatorFactory implements InstantiatorFactory
public Instantiator createInstantitor(VaadinService service)
public Class<? extends VaadinService> getServiceClass()

com.vaadin.cdi.CdiServletDeployer

// Added
@HandlesTypes(Route.class)
public class CdiServletDeployer implements ServletContainerInitializer
public void onStartup(Set<Class<?>> classSet, ServletContext ctx)

com.vaadin.cdi.CdiVaadinServlet

// Added
public class CdiVaadinServlet extends VaadinServlet
public void init(ServletConfig servletConfig) throws ServletException
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
public static String getCurrentServletName()
protected VaadinServletService createServletService(DeploymentConfiguration configuration) throws ServiceException

com.vaadin.cdi.CdiVaadinServletService

// Added
public class CdiVaadinServletService extends VaadinServletService
public CdiVaadinServletService(CdiVaadinServlet servlet, DeploymentConfiguration configuration, BeanManager beanManager)
public void init() throws ServiceException
protected VaadinSession loadSession(WrappedSession wrappedSession)
protected void storeSession(VaadinSession session, WrappedSession wrappedSession)
protected Executor createDefaultExecutor()
public Optional<Instantiator> loadInstantiators() throws ServiceException
public CdiVaadinServlet getServlet()

com.vaadin.cdi.CdiVaadinServletService.CdiVaadinServiceDelegate

// Added
public static class CdiVaadinServiceDelegate implements Serializable
public CdiVaadinServiceDelegate(BeanManager beanManager)
public void init(VaadinService vaadinService) throws ServiceException
public void addUIListeners(UI ui)
public <T> Optional<T> lookup(Class<T> type) throws ServiceException
public BeanManager getBeanManager()

com.vaadin.cdi.UIDetachEvent

// Added
public class UIDetachEvent extends DetachEvent
public UIDetachEvent(DetachEvent event)

com.vaadin.cdi.VaadinExtension

// Added
public class VaadinExtension implements Extension // all observer methods are private

com.vaadin.cdi.annotation.CdiComponent

// Added
@Stereotype @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface CdiComponent // no members

com.vaadin.cdi.annotation.UIScoped

// Added
@Scope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface UIScoped // no members

com.vaadin.cdi.annotation.NormalUIScoped

// Added
@NormalScope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface NormalUIScoped // no members

com.vaadin.cdi.annotation.RouteScoped

// Added
@Scope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface RouteScoped // no members

com.vaadin.cdi.annotation.NormalRouteScoped

// Added
@NormalScope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface NormalRouteScoped // no members

com.vaadin.cdi.annotation.RouteScopeOwner

// Added
@Qualifier @Retention(RUNTIME) @Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface RouteScopeOwner
Class<? extends HasElement> value()

com.vaadin.cdi.annotation.VaadinServiceScoped

// Added
@NormalScope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface VaadinServiceScoped // no members

com.vaadin.cdi.annotation.VaadinSessionScoped

// Added
@NormalScope @Inherited @Retention(RUNTIME)
@Target({ ANNOTATION_TYPE, TYPE, FIELD, METHOD, CONSTRUCTOR })
public @interface VaadinSessionScoped // no members

com.vaadin.cdi.annotation.VaadinServiceEnabled

// Added
@Qualifier @Retention(RUNTIME) @Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface VaadinServiceEnabled // no members

com.vaadin.cdi.context.ContextWrapper

// Added
public class ContextWrapper implements AlterableContext
public ContextWrapper(AbstractContext context, Class<? extends Annotation> scope)
public Class<? extends Annotation> getScope()
public <T> T get(Contextual<T> component, CreationalContext<T> creationalContext)
public <T> T get(Contextual<T> component)
public boolean isActive()
public void destroy(Contextual<?> contextual)

com.vaadin.cdi.context.UIScopedContext

// Added
public class UIScopedContext extends AbstractContext
public UIScopedContext(final BeanManager beanManager)
public void init(BeanManager beanManager)
public Class<? extends Annotation> getScope()
public boolean isActive()
protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)

// nested
@VaadinSessionScoped
public static class ContextualStorageManager extends AbstractContextualStorageManager<Integer>
public ContextualStorage getContextualStorage(boolean createIfNotExist)
protected ContextualStorage newContextualStorage(Integer uiId)

com.vaadin.cdi.context.RouteScopedContext

// Added
public class RouteScopedContext extends AbstractContext
public RouteScopedContext(BeanManager beanManager)
public void init(BeanManager beanManager, Supplier<Boolean> isUIContextActive)
public Class<? extends Annotation> getScope()
public boolean isActive()
protected List<ContextualStorage> getActiveContextualStorages()
protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)

// nested
@VaadinSessionScoped
public static class ContextualStorageManager extends AbstractContextualStorageManager<RouteStorageKey>
protected ContextualStorage newContextualStorage(RouteStorageKey key)

com.vaadin.cdi.context.VaadinServiceScopedContext

// Added
public class VaadinServiceScopedContext extends AbstractContext
public VaadinServiceScopedContext(BeanManager beanManager)
public void init(BeanManager beanManager)
public Class<? extends Annotation> getScope()
public boolean isActive()
protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)

// nested
@ApplicationScoped
public static class ContextualStorageManager extends AbstractContextualStorageManager<String>

com.vaadin.cdi.context.VaadinSessionScopedContext

// Added
public class VaadinSessionScopedContext extends AbstractContext
public VaadinSessionScopedContext(BeanManager beanManager)
public void init(BeanManager beanManager)
public Class<? extends Annotation> getScope()
public boolean isActive()
public static void destroy(VaadinSession session)
public static boolean guessContextIsUndeployed()
protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)

// nested
@ApplicationScoped
public static class ContextualStorageManager
protected static final String ATTRIBUTE_NAME
protected boolean isActive()
protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)

com.vaadin.cdi.util.AbstractContext

// Added
public abstract class AbstractContext implements Context
protected AbstractContext(BeanManager beanManager)
protected abstract ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)
protected List<ContextualStorage> getActiveContextualStorages()
protected void checkActive()
public boolean isPassivatingScope()
public <T> T get(Contextual<T> bean)
public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
public boolean destroy(Contextual bean)
public void destroyAllActive()
public static Map<Object, ContextualInstanceInfo<?>> destroyAllActive(ContextualStorage storage)
public static void destroyBean(Contextual bean, ContextualInstanceInfo<?> contextualInstanceInfo)

com.vaadin.cdi.util.AnyLiteral

// Added
public class AnyLiteral extends AnnotationLiteral<Any> implements Any

com.vaadin.cdi.util.BeanManagerProvider

// Added
public class BeanManagerProvider implements Extension
public static boolean isActive()
public static BeanManagerProvider getInstance()
public BeanManager getBeanManager()
public void setBeanManager(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager)
public void cleanupFinalBeanManagers(@Observes AfterDeploymentValidation adv)
public void cleanupStoredBeanManagerOnShutdown(@Observes BeforeShutdown beforeShutdown)

com.vaadin.cdi.util.BeanProvider

// Added
@Typed()
public final class BeanProvider // not instantiable
public static <T> T getContextualReference(Class<T> type, Annotation... qualifiers)
public static <T> T getContextualReference(Class<T> type, boolean optional, Annotation... qualifiers)
public static <T> T getContextualReference(BeanManager beanManager, Class<T> type, boolean optional, Annotation... qualifiers)
public static Object getContextualReference(String name)
public static Object getContextualReference(String name, boolean optional)
public static <T> T getContextualReference(String name, boolean optional, Class<T> type)
public static <T> T getContextualReference(BeanManager beanManager, String name, boolean optional, Class<T> type)
public static <T> T getContextualReference(Class<T> type, Bean<T> bean)
public static <T> List<T> getContextualReferences(Class<T> type, boolean optional)
public static <T> List<T> getContextualReferences(Class<T> type, boolean optional, boolean includeDefaultScopedBeans)
public static <T> DependentProvider<T> getDependent(Class<T> type, Annotation... qualifiers)
public static <T> DependentProvider<T> getDependent(BeanManager beanManager, Class<T> type, Annotation... qualifiers)
public static <T> DependentProvider<T> getDependent(String name)
public static <T> DependentProvider<T> getDependent(BeanManager beanManager, String name)
public static <T> Set<Bean<T>> getBeanDefinitions(Class<T> type, boolean optional, boolean includeDefaultScopedBeans)
public static <T> Set<Bean<T>> getBeanDefinitions(Class<T> type, boolean optional, boolean includeDefaultScopedBeans, BeanManager beanManager)
public static <T> T injectFields(T instance)

com.vaadin.cdi.util.ClassUtils

// Added
@Typed()
public abstract class ClassUtils // private ctor
public static ClassLoader getClassLoader(Object o)
public static boolean isProxyableClass(Type type)
public static <T> Class<T> tryToLoadClassForName(String name, Class<T> targetType)
public static <T> Class<T> tryToLoadClassForName(String name, Class<T> targetType, ClassLoader classLoader)
public static Class tryToLoadClassForName(String name)
public static Class tryToLoadClassForName(String name, ClassLoader classLoader)
public static Class loadClassForName(String name) throws ClassNotFoundException
public static <T> T tryToInstantiateClass(Class<T> targetClass)
public static <T> T tryToInstantiateClassForName(String className, Class<T> targetType)
public static Object tryToInstantiateClassForName(String className)
public static Object instantiateClassForName(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException
public static String getJarVersion(Class targetClass)
public static String getRevision(Class targetClass)
public static boolean containsMethod(Class<?> targetClass, Method method)
public static Method extractMethod(Class<?> clazz, Method sourceMethod)
public static Method extractMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes)
public static boolean containsPossiblyGenericMethod(Class<?> targetClass, Method method)
public static Method extractPossiblyGenericMethod(Class<?> clazz, Method sourceMethod)
public static boolean returns(Method method, Class<?> clazz)

com.vaadin.cdi.util.ContextUtils

// Added
@Typed()
public abstract class ContextUtils // private ctor
public static boolean isContextActive(Class<? extends Annotation> scopeAnnotationClass)
public static boolean isContextActive(Class<? extends Annotation> scopeAnnotationClass, BeanManager beanManager)

com.vaadin.cdi.util.ContextualInstanceInfo

// Added
public class ContextualInstanceInfo<T> implements Serializable
public CreationalContext<T> getCreationalContext()
public void setCreationalContext(CreationalContext<T> creationalContext)
public T getContextualInstance()
public void setContextualInstance(T contextualInstance)

com.vaadin.cdi.util.ContextualStorage

// Added
public class ContextualStorage implements Serializable
public ContextualStorage(BeanManager beanManager, boolean concurrent, boolean passivationCapable)
public Map<Object, ContextualInstanceInfo<?>> getStorage()
public boolean isConcurrent()
public <T> T createContextualInstance(Contextual<T> bean, CreationalContext<T> creationalContext)
public <T> Object getBeanKey(Contextual<T> bean)
public Contextual<?> getBean(Object beanKey)

com.vaadin.cdi.util.DependentProvider

// Added
public class DependentProvider<T> implements Provider<T>, Serializable // constructor is package-private
public T get()
public void destroy()

com.vaadin.cdi.util.ProxyUtils

// Added
@Typed()
public abstract class ProxyUtils // private ctor
public static Class getUnproxiedClass(Class currentClass)
public static boolean isProxiedClass(Class currentClass)
public static List<Class<?>> getProxyAndBaseTypes(Class<?> proxyClass)
public static boolean isInterfaceProxy(Class<?> proxyClass)

Test summary

# Status What the test verifies Why it matters
1 ✅ Resolving a bean that names its owner with @RouteScopeOwner before any navigation on the UI throws IllegalStateException, not NullPointerException This is the only behavior change in the PR; without the guard the error says nothing about which bean or owner failed
2 ✅ Firing an event whose only observer is Reception.IF_EXISTS before any navigation notifies nobody and does not throw The same guard sits before the && createIfNotExist short-circuit, so it also changed the no-create lookup path — the one containers actually take
3 ✅ The whole integration works on a real application server: CDI scopes and contexts, instantiator customization, push, i18n, templates, deployment validation, bean discovery mode Imported suite; the only thing that exercises com.vaadin.cdi.util at all, and the only check that the import still works per container
4 ✅ With no container profile active, the module reports its tests as skipped and runs none of them Otherwise a plain mvn verify over flow-tests fails for everyone; no other job covers this path
5 ✅ Each container profile's Arquillian adapter converges on a single Arquillian version Five profiles each pull their own adapter, so a divergence would only show up as confusing container failures
6 ❗ gap CdiVaadinServlet leaves no ThreadLocal entry after init and service return, and getCurrentServletName() still answers null A stale entry stays attached to a pooled container thread; the existing CdiVaadinServletTest never touches the servlet name
7 ❗ gap ClassUtils.extractPossiblyGenericMethod(null, sourceMethod) returns null instead of throwing extractMethod documents null as allowed, so a caller relying on that gets an NPE if the guard regresses

Tests added or changed on this branch:

  • RouteContextualStorageManagerTest.get_noNavigationDataYet_ownedBean_scopeDoesNotExist_Throws → 1
  • RouteContextualStorageManagerTest.customEvent_noNavigationDataYet_conditionalBean_doesNotThrow → 2

Both fail with the original NullPointerException if the guard is removed. Test 2 asserts by not throwing — there is no explicit Assertions call, because the point is that the IF_EXISTS lookup completes and notifies nobody. These two methods are the only new tests in this PR; the other 63 declared unit tests and all 58 Arquillian tests are imported unchanged, including ones whose commits look recent because the import grafted the upstream history.

Covered by CI steps rather than JUnit methods:

  • validation.yml → cdi-tests → "Run CDI ITs" (enforcer:enforce verify, per container profile) → 3, 5
  • validation.yml → cdi-tests → "Verify the no-container build skips its tests" → 4
  • xmlvalidate in the test module's sanitize-effective-pom execution fails the build if help:effective-pom output stops being parseable by ShrinkWrap, instead of failing inside every test's deployment method

Deliberately left untested: the remaining two fixes are behavior-neutral and have nothing to assert — making BeanManagerProvider's never-reassigned map field final, and deleting a recursion branch in getParentBeanManagerInfo that could not be reached. The DeltaSpike-derived com.vaadin.cdi.util package has no unit tests by design; only the container suite exercises it, which is why it is excluded from coverage measurement. One inherited weak spot worth knowing about, but out of scope here: CdiVaadinServletServiceTest.init_SystemMessagesProviderMissing_defaultConfigured asserts a ServiceException, which makes the default-provider assertion inside it unreachable.

Legioth and others added 30 commits November 11, 2014 15:01
Change-Id: If457da33cf5489c9b5cd552827fb60166599cd72
Change-Id: I326af890640a1b3e5fc8d96a2b3407b19785fc95
Using a nested VaadinCDIServlet is allowed.

Change-Id: I17261000feef668c7bd48b521eca3e3cf0a531f7
Change-Id: Ice24d701df7d3e0765c1493e1e75af69e7f54666
Change-Id: I17dfa70e98e64ea99bf897c48bdae7aad97a0f9d
Change-Id: Ic8199751125db735968297ae45b9a181c28d0313
Change-Id: Ic4fea717afa7389a17f82c2adf664f62f8198627
Change-Id: I37da96e890363029eddeaf76575100f237db2e21
Change-Id: Ic316f0fcfbbeebf96f5b33c6f34dc9e6fbe48d73
Change-Id: I04e4171168c021da9e690f553b1ff9c70412fdf9
Change-Id: I48f4623c9379c3a2bef11eda5601ace5458de644
Change-Id: Ibbcd930a52675ea1983ab38c027692a6f6758144
Change-Id: I3044360d37d69916e8a8c861539293681d9a5520
Change-Id: I5831d94061fa9070e5c740d742d2df0e905c3288
Change-Id: Id596cd1f38df95796905b057fd3d2818f84427d0
Change-Id: If5c8b606365247a74637174e1c49d49fc9ed510f
Change-Id: Ib5ca7086840fdd6320550522a6cfee504883a861
Change-Id: If4cac244c171bfb292c29f68dabafa54587edc55
Change-Id: Ifb600c961b6d6d95cbdb72cf3282f01a259640d8
Add checks for DenyAll to tighten security and have full support for the 
javax.security annotations.

Change-Id: I20627f4d0bec88a34524e484ffe56ea7076efdda
* Travis build for validation and master snapshots
* Fix path issue in deploy script
* Make sure all tests are run with TomEE 7

Rename tests and update dependencies to make all tests execute.

Use a recent Arquillian version and TomEE 7.
Ignore Shiro tests that are causing problems in validation builds with pre-releases - see #186.
The liberty leg failed the DependencyConvergence rule the Flow parent
enforces and the CDI parent did not: the Liberty adapter pulls the
Arquillian core in at 1.7.0.Final, diverging from what
arquillian-junit-container brings, across nine artifacts.

Importing arquillian-bom in place of the single managed entry pins all of
them. Nearest-wins already resolved them to arquillian.version, so this
records the version that was in effect rather than changing it, and it
holds for every container profile instead of only the ones that happen
not to declare an adapter of their own.

Convergence now passes for tomee, wildfly, payara, liberty and
tomcat-weld alike.
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

 1 472 files  + 30   1 556 suites  +30   1h 40m 53s ⏱️ + 6m 0s
12 237 tests +146  12 169 ✅ +146  68 💤 ±0  0 ❌ ±0 
12 555 runs  +146  12 487 ✅ +146  68 💤 ±0  0 ❌ ±0 

Results for commit 478d0ce. ± Comparison against base commit 30c0954.

♻️ This comment has been updated with latest results.

Declaring maven-antrun-plugin also activated the execution the flow-tests
parent manages, which deletes an exploded WAR's WEB-INF/lib. This module
is a jar and ShrinkWrap assembles its archives in process, so that
directory never exists and the execution printed an Ant BuildException
and a stack trace on every run - non-fatal, but indistinguishable from a
real failure in the log. Bound to phase none here.

The dependencyConvergence rule lives in the enforcer default-cli
execution, so no lifecycle phase evaluates it and nothing in CI would
have caught the Arquillian divergence the previous commit fixed, or a
regression of it. The CDI job now names the goal explicitly, which
evaluates it once per container profile - the profiles are what bring
the conflicting adapters. Confirmed it fails again if the BOM import is
removed.
@mshabarov

Copy link
Copy Markdown
Contributor

@claude do you see any blockers in this move - any concerns about CI, release, structure problems, test run and similar?

@claude

claude Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Claude finished @mshabarov's task in 2m 19s —— View job


Review: blockers & concerns for the CDI import

I read the CI wiring (validation.yml, computeMatrix.js), the module POMs (flow-tests/vaadin-cdi-tests/pom.xml, vaadin-cdi/pom.xml, flow-bom, root reactor, flow-tests) and the skip/gating logic. Overall this is a careful, well‑reasoned import — the tricky parts (effective‑POM sanitisation, Arquillian convergence, detached stdio, the no‑container skip) are all handled and explained. I don't see a hard structural blocker. The things worth deciding before merge are below, ordered by how likely they are to actually stop the merge.

🔴 Likely merge blockers (checks, not code)

  1. SonarQube quality gate is currently red. It fails on 57.4% coverage on new code (needs ≥80%) and a C reliability rating on new code. The 15k imported lines land without Flow‑measured coverage — the Arquillian ITs aren't wired into Sonar's JaCoCo, and the Weld unit tests only cover part of the runtime. If the Sonar gate is a required check, this blocks auto‑merge regardless of the tests passing. You'll likely need a Sonar new‑code exclusion for vaadin-cdi/** (imported code, analysed as if authored here) and a look at the handful of reliability issues Sonar flags. Worth an explicit decision rather than leaving it red.

  2. CLA not signed by all committers. thevaadinman and beth-soptim haven't signed. Because the branch carries the full upstream git history (632 commits), the CLA bot sees every historical author. This will keep the CLA check red until resolved/overridden.

🟠 Release / versioning — worth confirming before merge

  1. The artifact com.vaadin:vaadin-cdi now has two possible producers. It inherits flow-project 25.3-SNAPSHOT, so the GAV stays com.vaadin:vaadin-cdi but its version jumps to Flow's line and it releases on Flow's cadence. Two things to confirm:
    • The old vaadin/cdi repository must stop publishing that GAV, or two sources publish the same coordinates.
    • The Vaadin Platform BOM / release tooling that currently pins vaadin-cdi to its own version needs to switch to Flow's version (it's now managed in flow-bom). This is a downstream coordination step, not something this PR can fully close on its own.
    • The version scheme is discontinuous for existing consumers who depend on com.vaadin:vaadin-cdi directly — fine, but should be called out in release notes.

🟡 CI cost / robustness — by design, but note the trade‑offs

  1. Every Flow PR now waits on a real TomEE deploy. The tomee leg is ungated, and cdi-tests is a needs: of both test-results and cleanup-artifacts. So an application‑server download + startup is now on the critical path of every PR, and if TomEE is flaky it can flip the aggregate test-results check for changes that have nothing to do with CDI. The timeout-minutes: 45 / step 35 budget is generous, which is good, but this is the main new ongoing‑maintenance surface. Acceptable as the "one smoke container" strategy — just make sure cdi-tests (specifically the tomee leg) is what branch protection treats as required, and watch its flake rate early.

  2. Fork PRs don't exercise any of this. cdi-tests, it-tests, unit-tests and test-results are all gated on !github.event.pull_request.head.repo.fork. That's consistent with the existing jobs (they need the TB licence), so it's not a regression — but it means a fork contributor changing CDI sources gets zero CDI signal, and the gating script's fork branch is untested by construction. Fine to accept; just be aware.

  3. Gating fails open (intended). The non‑tomee legs run only when vaadin-cdi/, flow-tests/vaadin-cdi-tests/ or validation.yml changed, and run everything when no diff base is available. A wrong answer costs coverage/minutes, never correctness — the PR is explicit about this and it matches how gradle-tests gates. No concern.

🟢 Things I checked that are fine

  • Reactor & resolution. vaadin-cdi is a top‑level reactor module (pom.xml:53) and in flow-bom, so it's always built/installed; the cdi-tests job's -pl flow-tests/vaadin-cdi-tests (no -am) resolves because the saved workspace has vaadin-cdi and all parent/utility poms in .m2. ✅
  • Matrix exclusion. flow-tests/vaadin-cdi-tests is in globalExclusions (computeMatrix.js:13), so the generic it-tests matrix won't try to run it without a container. ✅
  • No‑container skip. cdi.container.tests.skip=true by default, flipped false only by the five container profiles; surefire honours it, and there's an explicit CI assertion that a profile‑less verify skips rather than runs/fails. ✅
  • Aggregate check wiring. test-results now includes cdi-tests in needs and its failure condition, and the detached‑stdio Run CDI ITs step exits with Maven's status (the tee bug is fixed), so a red container actually flips the check. ✅
  • Convergence enforcement. arquillian-bom import pins the nine core artifacts; enforcer:enforce is named explicitly in the job (the rule lives in default-cli, so nothing else evaluates it) and runs per container profile. ✅
  • Effective‑POM sanitisation and the phase‑none binding of the inherited delete-lib-folder antrun execution are both correct and well‑commented. ✅

One small nit (non‑blocking)

  • The Arquillian ITs are named *Test and run under surefire with skipTests, not failsafe/*IT. That's inherited from upstream and harmless, but it means a local mvn test -P<container> will spin up a container during the test phase, which can surprise contributors. Not worth changing as part of this import.

Bottom line

No structural blocker to the move itself — the build/reactor/CI plumbing is sound. The two things that will actually gate the merge are external to the code: the Sonar quality gate (coverage + reliability on new code) and the CLA. The one decision I'd want made explicitly is the release coordination (old vaadin/cdi stops publishing; platform BOM repoints to Flow's version), and the one thing to watch operationally is the ungated tomee leg now sitting on every PR's critical path.


Analysis based on the branch as of ec85beb0; I read files but did not run the container test suites.
· branch chore/import-cdi-to-flow

@mshabarov
mshabarov self-requested a review September 14, 2026 06:56
Comment thread vaadin-cdi/pom.xml Outdated
Artur- and others added 11 commits September 14, 2026 12:31
The bump to 25.4 reached vaadin-cdi but not its test module, which was
still declaring flow-tests 25.3-SNAPSHOT as its parent. A clean checkout
has no such parent to resolve, so the module would have failed the build
rather than joining the reactor at the current version.
Local tooling rewrote the lockfile while building and it was picked up by
the previous commit. Nothing in this branch touches flow-client, so this
puts the file back to what main has.
Seven of the eight bugs the analysis reports on this import are real and
fixable without changing behaviour:

- CdiVaadinServlet cleared its servlet-name ThreadLocal with set(null),
  which leaves an entry attached to the container's pooled thread. Uses
  remove() instead; getCurrentServletName() still answers null.
- RouteScopedContext dereferenced the UI's navigation data without
  checking for it. A bean that names its owner resolves it from the
  qualifier, so it reaches that check even before any navigation has
  happened, and failed with an NPE instead of the IllegalStateException
  naming the owner and the bean. Covered by a new test, which fails with
  the NPE when the guard is removed.
- BeanManagerProvider held its ClassLoader map in a volatile field that
  is never reassigned; the map is already concurrent, so it is final now.
  Its getParentBeanManagerInfo also recursed when getBeanManagerInfo
  returned null, which it cannot - it creates and stores an entry when
  one is missing - so that branch was dead.
- ClassUtils.extractPossiblyGenericMethod dereferenced a class its own
  extractMethod explicitly tolerates as null.

The eighth, DependentProvider writing its CreationalContext, stays: the
declared type is not Serializable but the instances containers supply
are, and dropping the write would leave a deserialized provider unable
to destroy its @dependent instance. Ignored for that one rule in that
one file.

Coverage is scoped to exclude com.vaadin.cdi.util, the DeltaSpike-derived
plumbing carried over with the import. What exercises it is the
Arquillian suite, and the analysis is fed unit-test coverage only, so
those lines read as untested no matter how much of them the containers
run. The rest of the module is Vaadin's own code and stays measured.
The guard in navigationChainHasOwner is evaluated before the
&& createIfNotExist short-circuit, so it changed two paths and only the
create one was pinned. The other is the one that matters in practice:
AbstractContext.get(Contextual) looks storage up with createIfNotExist
false, which is how the container resolves an IF_EXISTS observer, and
firing such an event before any navigation used to fail with an NPE.
Both tests now fail with that NPE if the guard is neutralised.

Also corrects the javadoc on getParentBeanManagerInfo, which still
described the recursion that was removed and claimed a null return for a
ClassLoader hierarchy without a BeanManagerInfo - it creates one, and
answers null only for a ClassLoader with no parent. And states the null
precondition once at the top of extractPossiblyGenericMethod instead of
nesting it inside the fallback branch, matching the shape isProxyableClass
already uses.
The job was written against an older base, so its Node and pnpm pins had
drifted behind the ones every other job in the file uses.

A bump of weld.version in the root pom reaches the tomcat-weld profile
through weld-servlet-shaded, so the root pom now counts as a CDI source
and runs the gated containers.

tomee is ungated and never diffs, so it no longer clones the full
history. cleanup-artifacts already reaches cdi-tests through
test-results.
vaadin-cdi kept its own release scheme under the same GAV -- 46 releases
from 1.0.0.alpha1 to 16.1.2 -- before it started releasing with Flow's
version number. On the shared axis those versions land in the gap
between Flow 9.2 and 23.0, which breaks the "present" streak the tool
derives @SInCE from and rewrites the tags across most of the framework.
An isolated index-dir keeps them off it, the same way vaadin-spring is
handled.
…output

build-helper:parse-version sets properties whose names end in a question
mark, which is a legal Maven property name but not a legal XML element
name, so help:effective-pom writes tags the strict POM reader ShrinkWrap
uses rejects. Not producing them is cheaper than matching them with a
regular expression that has to guess how Maven serialises an element.

Nothing in this module reads ${parsedVersion.*}: osgi.bundle.version is
its only consumer in the repository and bnd reads that from a bnd.bnd,
which this module does not have. bnd still derives Bundle-Version from
the project version.

The remaining replaceregexp strips the duplicate XML declaration, and
xmlvalidate still fails the build if the output changes shape again --
an illegal element name from another plugin included.
Without a scope the adapter is a compile dependency, so it reaches the
compile classpath and the archive ShrinkWrap assembles, neither of which
has any use for it. Verified with the liberty profile: 57 tests, all
passing.

The concurrency API in the tomcat-weld profile has no scope on purpose,
which reads the same and is not. Documented, because removing it breaks
the module: Tomcat with Weld supplies no Jakarta Concurrency, so the
provided scope declared for the rest of the containers has to be
promoted to compile there.
The module map pointed at a future test-cdi module as the home for CDI
tests. That module is here now under a different name, so both the
decision list and the target table point at it.

Running the suite locally needs JDK 21, the version validation uses. On
JDK 24 and later java.security.Policy.setPolicy throws
UnsupportedOperationException, and TomEE calls it while installing its
JACC policy provider, so the container never starts and all 57 tests
fail with a message that says nothing about CDI.
@mshabarov

Copy link
Copy Markdown
Contributor

@claude re-check previous comments - what is not yet fixed?

@claude

claude Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Claude finished @mshabarov's task in 1m 16s —— View job


Re-check of earlier comments — what's fixed vs. still open

I went back through Artur's review and my 2026‑09‑11 analysis and checked each item against the branch as it stands now (478d0ce1).

✅ Fixed since the earlier comments

Item Raised by Status
Bump vaadin-cdi to 25.4-SNAPSHOT Artur- Applied — vaadin-cdi/pom.xml:8 is 25.4-SNAPSHOT
Sibling flow-tests/vaadin-cdi-tests parent still on 25.3 author follow‑up Applied — parent is 25.4-SNAPSHOT (flow-tests/vaadin-cdi-tests/pom.xml:7); grep finds no 25.3-SNAPSHOT left anywhere under vaadin-cdi/ or the test module
Sonar quality gate red (57.4% coverage, C reliability) my analysis (blocker #1) Green now — Sonar comment at 2026‑09‑14 reports Quality Gate passed, 81.3% coverage on new code, 0 duplication
Reliability fix: ThreadLocal.remove() (test gap row 3) PR body Present — CdiVaadinServlet.java:56,67 call servletName.remove()
Reliability fix: ClassUtils.extractPossiblyGenericMethod(null,…) (row 4) PR body Present — null guard at ClassUtils.java:472 returning null
Reliability fix: BeanManagerProvider final map + dead recursion (row 5) PR body Present — bmInfos is private final … ConcurrentHashMap (BeanManagerProvider.java:146)
Reliability fix: RouteScopedContext missing-navigation guard PR body Present — IllegalStateException on the create path, no throw on the lookup path (RouteScopedContext.java:360‑400)

All of the code-level items from both threads are in. CI on the latest commit: Formatter ✅, Validation still in progress, no failures.

🟠 Still open — external / not closable by this PR

  1. CLA — last bot status was 6/8 signed (beth-soptim, thevaadinman missing). Because the branch carries the full upstream vaadin/cdi history, the bot sees every historical author. This needs either the signatures or a maintainer override; nothing in the code can resolve it.
  2. Release coordination (my 🟠 Event data should not be sent for each element instance separately #3) — still a decision to make outside this repo:
    • the old vaadin/cdi repo must stop publishing com.vaadin:vaadin-cdi, or two sources publish the same GAV;
    • the Platform BOM / release tooling must repoint vaadin-cdi to Flow's version (now managed in flow-bom);
    • the version jump (its own line → 25.4) is discontinuous for direct consumers and is worth a release-note line.

🟡 Accepted trade-offs (not "unfixed" — decisions to confirm, not change)

  • Ungated tomee leg on every PR's critical path — still the design; the operational watch item is its flake rate. Confirm branch protection treats the tomee leg (via test-results) as the required signal.
  • Fork PRs get no CDI signal — consistent with the existing TB-licensed jobs, not a regression.
  • Gating fails open — intended.
  • Arquillian ITs named *Test under surefire — inherited from upstream, harmless nit.

Bottom line

Everything that was actionable in the code — Artur's version bump (plus the sibling POM the author caught), the Sonar gate, and all four reliability fixes — is fixed and verified on the branch. The only genuinely outstanding items are the two external ones: the CLA signatures and the release/BOM coordination. Neither can be closed from within this PR.
· branch chore/import-cdi-to-flow

@sonarqubecloud

Copy link
Copy Markdown

@mshabarov

Copy link
Copy Markdown
Contributor

Looks good to me, need to change from squash to merge with a short window.

@Artur-
Artur- merged commit 3e52c0c into main Sep 14, 2026
41 of 42 checks passed
@Artur-
Artur- deleted the chore/import-cdi-to-flow branch September 14, 2026 13:50
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

Thanks @mshabarov — agreed, and there are two separate settings in the way, so flagging both before the window opens.

  1. allow_merge_commit is currently false on this repository, so "Create a merge commit" is not selectable at all right now.
  2. The merge queue on main is configured with mergeMethod: SQUASH. Adding this PR to the queue therefore squashes it even after merge commits are re-enabled — so the merge has to bypass the queue, or the queue's method has to be switched for the same short window.

What is at stake concretely: the import is a merge commit (e69fb4f) whose second parent is vaadin/cdi's history, which is what keeps the CDI sources attributable. Today git log --follow vaadin-cdi/src/main/java/com/vaadin/cdi/CdiInstantiator.java still reaches commits from 2018. A squash collapses all 644 commits into one and discards that second parent, so every CDI file's history would begin at the import. A rebase merge is not an alternative either — rebase drops merge commits, so it would linearise the imported history onto Flow's rather than record it as an import.

No code change for this one; it is purely a settings action and I have no admin rights here. Everything else on the branch is green.

javier-godoy pushed a commit to javier-godoy/flow that referenced this pull request Sep 29, 2026
Brings vaadin/quarkus main into this repository the same way vaadin/cdi was
imported in vaadin#25525: a merge of the upstream history so `git log` and
`git blame` keep working for the Quarkus extension sources.

The extension lands at /vaadin-quarkus, keeping upstream's runtime and
deployment split, and the integration tests at
/flow-tests/vaadin-quarkus-tests. The relocation happens in this merge
commit without touching file contents, so rename detection carries the
history across it.

The upstream root pom, formatter configuration and validation workflow are
replaced by Flow's in the following commits. The ecosystem CI workflow and
its test script are kept and adapted there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.