Skip to content

Commit bbbf965

Browse files
authored
Merge pull request #32407 from manovotn/arcTckFixes
Arc CDI TCK related fixes, focused around behavior gated behind strict mode
2 parents 5c69f84 + 259ef94 commit bbbf965

File tree

13 files changed

+174
-77
lines changed

13 files changed

+174
-77
lines changed

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,9 @@ public void registerSubclass(DotName beanClassName, String subclassName) {
637637
reflectiveClasses
638638
.produce(ReflectiveClassBuildItem.builder(binding.name().toString()).methods().build());
639639
}
640-
641640
ArcContainer container = recorder.initContainer(shutdown,
642-
currentContextFactory.isPresent() ? currentContextFactory.get().getFactory() : null);
641+
currentContextFactory.isPresent() ? currentContextFactory.get().getFactory() : null,
642+
config.strictCompatibility);
643643
BeanContainer beanContainer = recorder.initBeanContainer(container,
644644
beanContainerListenerBuildItems.stream().map(BeanContainerListenerBuildItem::getBeanContainerListener)
645645
.collect(Collectors.toList()));

extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import io.quarkus.arc.Arc;
1717
import io.quarkus.arc.ArcContainer;
18+
import io.quarkus.arc.ArcInitConfig;
1819
import io.quarkus.arc.CurrentContextFactory;
1920
import io.quarkus.arc.InjectableBean;
2021
import io.quarkus.arc.InjectableBean.Kind;
@@ -40,9 +41,12 @@ public class ArcRecorder {
4041
*/
4142
public static volatile Map<String, Function<SyntheticCreationalContext<?>, ?>> syntheticBeanProviders;
4243

43-
public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory)
44+
public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory,
45+
boolean strictCompatibility)
4446
throws Exception {
45-
ArcContainer container = Arc.initialize(currentContextFactory != null ? currentContextFactory.getValue() : null);
47+
ArcContainer container = Arc.initialize(ArcInitConfig.builder()
48+
.setCurrentContextFactory(currentContextFactory != null ? currentContextFactory.getValue() : null)
49+
.setStrictCompatibility(strictCompatibility).build());
4650
shutdown.addShutdownTask(new Runnable() {
4751
@Override
4852
public void run() {

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java

+50-22
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,14 @@ private List<BeanInfo> findBeans(Collection<DotName> beanDefiningAnnotations, Li
938938
}
939939
}
940940

941-
// a bean without no-arg constructor needs to have either a constructor annotated with @Inject
942-
// or a single constructor
941+
// in strict compatibility mode, the bean needs to have either no args ctor or some with @Inject
942+
// note that we perform validation (for multiple ctors for instance) later in the cycle
943+
if (strictCompatibility && numberOfConstructorsWithInject == 0) {
944+
continue;
945+
}
946+
947+
// without strict compatibility, a bean without no-arg constructor needs to have either a constructor
948+
// annotated with @Inject or a single constructor
943949
if (numberOfConstructorsWithInject == 0 && numberOfConstructorsWithoutInject != 1) {
944950
continue;
945951
}
@@ -982,12 +988,19 @@ private List<BeanInfo> findBeans(Collection<DotName> beanDefiningAnnotations, Li
982988
}
983989
if (annotationStore.hasAnnotation(method, DotNames.PRODUCES)
984990
&& !annotationStore.hasAnnotation(method, DotNames.VETOED_PRODUCER)) {
991+
// Do not register classes with producers and no bean def. annotation as beans in strict mode
985992
// Producers are not inherited
986-
producerMethods.add(method);
987-
if (!hasBeanDefiningAnnotation) {
988-
LOGGER.debugf("Producer method found but %s has no bean defining annotation - using @Dependent",
989-
beanClass);
990-
beanClasses.add(beanClass);
993+
if (strictCompatibility) {
994+
if (hasBeanDefiningAnnotation) {
995+
producerMethods.add(method);
996+
}
997+
} else {
998+
producerMethods.add(method);
999+
if (!hasBeanDefiningAnnotation) {
1000+
LOGGER.debugf("Producer method found but %s has no bean defining annotation - using @Dependent",
1001+
beanClass);
1002+
beanClasses.add(beanClass);
1003+
}
9911004
}
9921005
}
9931006
if (annotationStore.hasAnnotation(method, DotNames.DISPOSES)) {
@@ -1025,23 +1038,31 @@ private List<BeanInfo> findBeans(Collection<DotName> beanDefiningAnnotations, Li
10251038
if (annotationStore.hasAnnotation(method, DotNames.OBSERVES)) {
10261039
syncObserverMethods.computeIfAbsent(method, ignored -> new HashSet<>())
10271040
.add(beanClass);
1041+
// add only concrete classes
10281042
if (!Modifier.isAbstract(beanClass.flags())) {
1029-
// add only concrete classes
1030-
beanClasses.add(beanClass);
1031-
if (!hasBeanDefiningAnnotation) {
1032-
LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent",
1033-
beanClass);
1043+
// do not register classes with observers and no bean def. annotation as beans in strict mode
1044+
if (!strictCompatibility) {
1045+
beanClasses.add(beanClass);
1046+
if (!hasBeanDefiningAnnotation) {
1047+
LOGGER.debugf(
1048+
"Observer method found but %s has no bean defining annotation - using @Dependent",
1049+
beanClass);
1050+
}
10341051
}
10351052
}
10361053
} else if (annotationStore.hasAnnotation(method, DotNames.OBSERVES_ASYNC)) {
10371054
asyncObserverMethods.computeIfAbsent(method, ignored -> new HashSet<>())
10381055
.add(beanClass);
1056+
// add only concrete classes
10391057
if (!Modifier.isAbstract(beanClass.flags())) {
1040-
// add only concrete classes
1041-
beanClasses.add(beanClass);
1042-
if (!hasBeanDefiningAnnotation) {
1043-
LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent",
1044-
beanClass);
1058+
// do not register classes with observers and no bean def. annotation as beans in strict mode
1059+
if (!strictCompatibility) {
1060+
beanClasses.add(beanClass);
1061+
if (!hasBeanDefiningAnnotation) {
1062+
LOGGER.debugf(
1063+
"Observer method found but %s has no bean defining annotation - using @Dependent",
1064+
beanClass);
1065+
}
10451066
}
10461067
}
10471068
}
@@ -1059,12 +1080,19 @@ private List<BeanInfo> findBeans(Collection<DotName> beanDefiningAnnotations, Li
10591080
if (annotationStore.hasAnnotation(field, DotNames.INJECT)) {
10601081
throw new DefinitionException("Injected field cannot be annotated with @Produces: " + field);
10611082
}
1083+
// Do not register classes with producers and no bean def. annotation as beans in strict mode
10621084
// Producer fields are not inherited
1063-
producerFields.add(field);
1064-
if (!hasBeanDefiningAnnotation) {
1065-
LOGGER.debugf("Producer field found but %s has no bean defining annotation - using @Dependent",
1066-
beanClass);
1067-
beanClasses.add(beanClass);
1085+
if (strictCompatibility) {
1086+
if (hasBeanDefiningAnnotation) {
1087+
producerFields.add(field);
1088+
}
1089+
} else {
1090+
producerFields.add(field);
1091+
if (!hasBeanDefiningAnnotation) {
1092+
LOGGER.debugf("Producer field found but %s has no bean defining annotation - using @Dependent",
1093+
beanClass);
1094+
beanClasses.add(beanClass);
1095+
}
10681096
}
10691097
} else {
10701098
// Verify that non-producer fields are not annotated with stereotypes

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.function.Supplier;
2424
import java.util.stream.Collectors;
2525

26-
import jakarta.enterprise.context.ContextNotActiveException;
2726
import jakarta.enterprise.context.spi.Contextual;
2827
import jakarta.enterprise.event.Reception;
2928
import jakarta.enterprise.event.TransactionPhase;
@@ -414,8 +413,8 @@ protected void implementNotify(ObserverInfo observer, ClassCreator observerCreat
414413
ResultHandle context = notify.invokeInterfaceMethod(MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT,
415414
container,
416415
scope);
417-
notify.ifNull(context).trueBranch().throwException(ContextNotActiveException.class,
418-
"Context not active: " + observer.getDeclaringBean().getScope().getDotName());
416+
// if the context isn't active, don't notify the observer
417+
notify.ifNull(context).trueBranch().returnVoid();
419418
notify.assign(declaringProviderInstanceHandle,
420419
notify.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET_IF_PRESENT, context,
421420
declaringProviderHandle));

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Arc.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,37 @@ public final class Arc {
1212

1313
private static final AtomicReference<ArcContainerImpl> INSTANCE = new AtomicReference<>();
1414

15+
/**
16+
* Initializes {@link ArcContainer} with default settings.
17+
* This is equal to using {@code Arc#initialize(ArcInitConfig.INSTANCE)}
18+
*
19+
* @return {@link ArcContainer} instance with default configuration
20+
*/
1521
public static ArcContainer initialize() {
16-
return initialize(null);
22+
return initialize(ArcInitConfig.DEFAULT);
1723
}
1824

1925
/**
26+
* Deprecated, will be removed in future iterations.
27+
* Users are encouraged to use {@link #initialize(ArcInitConfig)} instead.
2028
*
2129
* @param currentContextFactory
2230
* @return the initialized container
2331
*/
32+
@Deprecated(since = "3.0", forRemoval = true)
2433
public static ArcContainer initialize(CurrentContextFactory currentContextFactory) {
34+
return initialize(ArcInitConfig.builder().setCurrentContextFactory(currentContextFactory).build());
35+
}
36+
37+
public static ArcContainer initialize(ArcInitConfig arcInitConfig) {
2538
ArcContainerImpl container = INSTANCE.get();
2639
if (container == null) {
2740
synchronized (INSTANCE) {
2841
container = INSTANCE.get();
2942
if (container == null) {
3043
// Set the container instance first because Arc.container() can be used within ArcContainerImpl.init()
31-
container = new ArcContainerImpl(currentContextFactory);
44+
container = new ArcContainerImpl(arcInitConfig.getCurrentContextFactory(),
45+
arcInitConfig.isStrictCompatibility());
3246
INSTANCE.set(container);
3347
container.init();
3448
}

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainer.java

+8
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,12 @@ public interface ArcContainer {
233233
* @see CurrentContext
234234
*/
235235
CurrentContextFactory getCurrentContextFactory();
236+
237+
/**
238+
* Indicates whether container runs in strict compatibility mode.
239+
* Default value is false.
240+
*
241+
* @return true is strict mode is enabled, false otherwise.
242+
*/
243+
boolean strictCompatibility();
236244
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.quarkus.arc;
2+
3+
/**
4+
* A configuration object used while initializing Arc, see {@link Arc#initialize()} methods.
5+
* Consolidates all configuration objects needed for Arc to initialize, values are initialized to their defaults.
6+
*
7+
*/
8+
public final class ArcInitConfig {
9+
10+
/**
11+
* Basic instance without any configuration, all values are default
12+
*/
13+
public static final ArcInitConfig DEFAULT = builder().build();
14+
15+
/**
16+
* Obtains a builder for {@link ArcInitConfig}
17+
*
18+
* @return new instance of the builder
19+
*/
20+
public static Builder builder() {
21+
return new Builder();
22+
}
23+
24+
private ArcInitConfig(Builder builder) {
25+
this.currentContextFactory = builder.currentContextFactory;
26+
this.strictCompatibility = builder.strictCompatibility;
27+
}
28+
29+
private final boolean strictCompatibility;
30+
private final CurrentContextFactory currentContextFactory;
31+
32+
public boolean isStrictCompatibility() {
33+
return strictCompatibility;
34+
}
35+
36+
public CurrentContextFactory getCurrentContextFactory() {
37+
return currentContextFactory;
38+
}
39+
40+
public static class Builder {
41+
private boolean strictCompatibility;
42+
private CurrentContextFactory currentContextFactory;
43+
44+
private Builder() {
45+
// init all values with their defaults
46+
this.strictCompatibility = false;
47+
this.currentContextFactory = null;
48+
}
49+
50+
public Builder setStrictCompatibility(boolean strictCompatibility) {
51+
this.strictCompatibility = strictCompatibility;
52+
return this;
53+
}
54+
55+
public Builder setCurrentContextFactory(CurrentContextFactory currentContextFactory) {
56+
this.currentContextFactory = currentContextFactory;
57+
return this;
58+
}
59+
60+
public ArcInitConfig build() {
61+
return new ArcInitConfig(this);
62+
}
63+
}
64+
}

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ default TransactionPhase getTransactionPhase() {
3838

3939
@Override
4040
default Bean<?> getDeclaringBean() {
41-
return Arc.container().bean(getDeclaringBeanIdentifier());
41+
return getDeclaringBeanIdentifier() != null ? Arc.container().bean(getDeclaringBeanIdentifier()) : null;
4242
}
4343

4444
default void notify(T event) {

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ public class ArcContainerImpl implements ArcContainer {
9898

9999
private final CurrentContextFactory currentContextFactory;
100100

101-
public ArcContainerImpl(CurrentContextFactory currentContextFactory) {
101+
private final boolean strictMode;
102+
103+
public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean strictMode) {
104+
this.strictMode = strictMode;
102105
id = String.valueOf(ID_GENERATOR.incrementAndGet());
103106
running = new AtomicBoolean(true);
104107
List<InjectableBean<?>> beans = new ArrayList<>();
@@ -363,6 +366,11 @@ public CurrentContextFactory getCurrentContextFactory() {
363366
return currentContextFactory;
364367
}
365368

369+
@Override
370+
public boolean strictCompatibility() {
371+
return strictMode;
372+
}
373+
366374
@Override
367375
public String toString() {
368376
return "ArcContainerImpl [id=" + id + ", running=" + running + ", beans=" + beans.size() + ", observers="

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ private Notifier<? super T> createNotifier(Class<?> runtimeType) {
166166

167167
static <T> Notifier<T> createNotifier(Class<?> runtimeType, Type eventType, Set<Annotation> qualifiers,
168168
ArcContainerImpl container, InjectionPoint injectionPoint) {
169-
return createNotifier(runtimeType, eventType, qualifiers, container, true, injectionPoint);
169+
return createNotifier(runtimeType, eventType, qualifiers, container, !Arc.container().strictCompatibility(),
170+
injectionPoint);
170171
}
171172

172173
static <T> Notifier<T> createNotifier(Class<?> runtimeType, Type eventType, Set<Annotation> qualifiers,
@@ -316,6 +317,7 @@ void notify(T event, ObserverExceptionHandler exceptionHandler, boolean async) {
316317
}
317318

318319
// Non-tx observers notifications
320+
// req. context is activated if not in strict mode and not for lifecycle events such as init/shutdown
319321
if (activateRequestContext) {
320322
ManagedContext requestContext = Arc.container().requestContext();
321323
if (requestContext.isActive()) {

independent-projects/arc/tcks/arquillian/src/main/java/io/quarkus/arc/arquillian/ArcDeployableContainer.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import io.quarkus.arc.Arc;
2323
import io.quarkus.arc.ArcContainer;
24+
import io.quarkus.arc.ArcInitConfig;
2425
import io.quarkus.arc.InjectableInstance;
2526
import io.quarkus.arc.InstanceHandle;
2627
import io.quarkus.arc.arquillian.utils.ClassLoading;
@@ -81,7 +82,8 @@ public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
8182

8283
Thread.currentThread().setContextClassLoader(deploymentClassLoader);
8384

84-
ArcContainer arcContainer = Arc.initialize();
85+
// passing strict mode here allows it to be visible in runtime
86+
ArcContainer arcContainer = Arc.initialize(ArcInitConfig.builder().setStrictCompatibility(true).build());
8587
runningArc.set(arcContainer);
8688
arcContainer.beanManager().getEvent().fire(new Startup());
8789

independent-projects/arc/tcks/cdi-tck-porting-pkg/src/main/java/io/quarkus/arc/tck/porting/ContextsImpl.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.quarkus.arc.tck.porting;
22

33
import java.lang.annotation.Annotation;
4+
import java.util.Map;
5+
import java.util.concurrent.ConcurrentHashMap;
46

57
import jakarta.enterprise.context.Dependent;
68
import jakarta.enterprise.context.spi.Context;
@@ -14,13 +16,20 @@
1416
import io.quarkus.arc.ManagedContext;
1517

1618
public class ContextsImpl implements Contexts<Context> {
19+
20+
// ConcurrentHashMap is just future-proofing, could be implemented with plain map too
21+
private final Map<Context, InjectableContext.ContextState> contextStateMap = new ConcurrentHashMap<>();
22+
1723
@Override
1824
public void setActive(Context context) {
19-
((ManagedContext) context).activate();
25+
// remove the context state we potentially stored, else use null to initiate fresh context
26+
((ManagedContext) context).activate(contextStateMap.remove(context));
2027
}
2128

2229
@Override
2330
public void setInactive(Context context) {
31+
// save the state of the context
32+
contextStateMap.put(context, ((ManagedContext) context).getState());
2433
((ManagedContext) context).deactivate();
2534
}
2635

0 commit comments

Comments
 (0)