Skip to content

Commit 4951446

Browse files
committed
Intercepted methods of a bean may not be declared final
- otherwise a deployment exception is thrown - final methods declared on bean classes that require client proxies are ignored though
1 parent 9ed6438 commit 4951446

File tree

5 files changed

+192
-16
lines changed

5 files changed

+192
-16
lines changed

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.util.Set;
2020
import java.util.concurrent.ConcurrentHashMap;
2121
import java.util.function.Consumer;
22+
import java.util.stream.Collectors;
2223
import javax.enterprise.inject.spi.DefinitionException;
24+
import javax.enterprise.inject.spi.DeploymentException;
2325
import javax.enterprise.inject.spi.InterceptionType;
2426
import org.jboss.jandex.AnnotationInstance;
2527
import org.jboss.jandex.AnnotationTarget;
@@ -405,8 +407,10 @@ void init(List<Throwable> errors) {
405407
if (disposer != null) {
406408
disposer.init(errors);
407409
}
408-
interceptedMethods.putAll(initInterceptedMethods());
409-
lifecycleInterceptors.putAll(initLifecycleInterceptors());
410+
interceptedMethods.putAll(initInterceptedMethods(errors));
411+
if (errors.isEmpty()) {
412+
lifecycleInterceptors.putAll(initLifecycleInterceptors());
413+
}
410414
}
411415

412416
protected String getType() {
@@ -421,7 +425,7 @@ protected String getType() {
421425
}
422426
}
423427

424-
private Map<MethodInfo, InterceptionInfo> initInterceptedMethods() {
428+
private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable> errors) {
425429
if (!isInterceptor() && isClassBean()) {
426430
Map<MethodInfo, InterceptionInfo> interceptedMethods = new HashMap<>();
427431
Map<MethodKey, Set<AnnotationInstance>> candidates = new HashMap<>();
@@ -434,7 +438,14 @@ private Map<MethodInfo, InterceptionInfo> initInterceptedMethods() {
434438
}
435439
}
436440

437-
Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(), candidates, classLevelBindings);
441+
Set<MethodInfo> finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(),
442+
candidates, classLevelBindings);
443+
if (!finalMethods.isEmpty()) {
444+
errors.add(new DeploymentException(String.format(
445+
"Intercepted methods of the bean %s may not be declared final:\n\t- %s", getBeanClass(),
446+
finalMethods.stream().map(Object::toString).sorted().collect(Collectors.joining("\n\t- ")))));
447+
return Collections.emptyMap();
448+
}
438449

439450
for (Entry<MethodKey, Set<AnnotationInstance>> entry : candidates.entrySet()) {
440451
List<InterceptorInfo> interceptors = beanDeployment.getInterceptorResolver()

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ private static boolean skipForClientProxy(MethodInfo method) {
100100
if (Modifier.isFinal(method.flags())) {
101101
String className = method.declaringClass().name().toString();
102102
if (!className.startsWith("java.")) {
103-
LOGGER.warn(
104-
String.format("Method %s.%s() is final, skipped during generation of the corresponding client proxy",
105-
className, method.name()));
103+
LOGGER.warn(String.format(
104+
"Final method %s.%s() is ignored during proxy generation and should never be invoked upon the proxy instance!",
105+
className, method.name()));
106106
}
107107
return true;
108108
}
@@ -113,9 +113,10 @@ static boolean isObjectToString(MethodInfo method) {
113113
return method.declaringClass().name().equals(DotNames.OBJECT) && method.name().equals(TO_STRING);
114114
}
115115

116-
static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo,
116+
static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo,
117117
Map<MethodKey, Set<AnnotationInstance>> candidates,
118118
List<AnnotationInstance> classLevelBindings) {
119+
Set<MethodInfo> finalMethods = new HashSet<>();
119120
for (MethodInfo method : classInfo.methods()) {
120121
if (skipForSubclass(method)) {
121122
continue;
@@ -134,13 +135,7 @@ static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassI
134135
}
135136
if (!merged.isEmpty()) {
136137
if (Modifier.isFinal(method.flags())) {
137-
String className = method.declaringClass().name().toString();
138-
if (!className.startsWith("java.")) {
139-
LOGGER.warn(
140-
String.format(
141-
"Method %s.%s() is final, skipped during generation of the corresponding intercepted subclass",
142-
className, method.name()));
143-
}
138+
finalMethods.add(method);
144139
} else {
145140
candidates.computeIfAbsent(new Methods.MethodKey(method), key -> merged);
146141
}
@@ -149,9 +144,11 @@ static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassI
149144
if (classInfo.superClassType() != null) {
150145
ClassInfo superClassInfo = getClassByName(beanDeployment.getIndex(), classInfo.superName());
151146
if (superClassInfo != null) {
152-
addInterceptedMethodCandidates(beanDeployment, superClassInfo, candidates, classLevelBindings);
147+
finalMethods
148+
.addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, candidates, classLevelBindings));
153149
}
154150
}
151+
return finalMethods;
155152
}
156153

157154
private static boolean skipForSubclass(MethodInfo method) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.quarkus.arc.test.clientproxy.finalmethod;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import io.quarkus.arc.Arc;
7+
import io.quarkus.arc.ClientProxy;
8+
import io.quarkus.arc.test.ArcTestContainer;
9+
import java.io.IOException;
10+
import javax.annotation.PostConstruct;
11+
import javax.enterprise.context.ApplicationScoped;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.RegisterExtension;
14+
15+
public class FinalMethodIgnoredTest {
16+
17+
@RegisterExtension
18+
public ArcTestContainer container = new ArcTestContainer(Moo.class);
19+
20+
@Test
21+
public void testProducer() throws IOException {
22+
Moo moo = Arc.container().instance(Moo.class).get();
23+
assertTrue(moo instanceof ClientProxy);
24+
assertEquals(0, moo.getVal());
25+
assertEquals(10, ((Moo) ((ClientProxy) moo).arc_contextualInstance()).val);
26+
}
27+
28+
@ApplicationScoped
29+
static class Moo {
30+
31+
private int val;
32+
33+
@PostConstruct
34+
void init() {
35+
this.val = 10;
36+
}
37+
38+
// will return 0 if invoked upon a client proxy
39+
final int getVal() {
40+
return val;
41+
}
42+
43+
}
44+
45+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.quarkus.arc.test.interceptors.finalmethod;
2+
3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import io.quarkus.arc.test.ArcTestContainer;
7+
import io.quarkus.arc.test.interceptors.Simple;
8+
import javax.annotation.Priority;
9+
import javax.enterprise.inject.spi.DeploymentException;
10+
import javax.inject.Singleton;
11+
import javax.interceptor.AroundInvoke;
12+
import javax.interceptor.Interceptor;
13+
import javax.interceptor.InvocationContext;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.RegisterExtension;
16+
17+
public class FinalInterceptedMethodTest {
18+
19+
@RegisterExtension
20+
public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Simple.class, SimpleBean.class,
21+
SimpleInterceptor.class).shouldFail().build();
22+
23+
@Test
24+
public void testFailure() {
25+
Throwable t = container.getFailure();
26+
assertNotNull(t);
27+
assertTrue(t instanceof DeploymentException);
28+
assertTrue(t.getMessage().contains("foo"));
29+
assertTrue(t.getMessage().contains("bar"));
30+
}
31+
32+
@Simple
33+
@Singleton
34+
static class SimpleBean {
35+
36+
final String foo() {
37+
return "foo";
38+
}
39+
40+
final void bar() {
41+
}
42+
43+
}
44+
45+
@Simple
46+
@Priority(1)
47+
@Interceptor
48+
static class SimpleInterceptor {
49+
50+
@AroundInvoke
51+
Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception {
52+
return ctx.proceed();
53+
}
54+
}
55+
56+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.quarkus.arc.test.interceptors.finalmethod;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import javax.annotation.PostConstruct;
6+
import javax.annotation.Priority;
7+
import javax.inject.Singleton;
8+
import javax.interceptor.AroundInvoke;
9+
import javax.interceptor.Interceptor;
10+
import javax.interceptor.InvocationContext;
11+
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.RegisterExtension;
14+
15+
import io.quarkus.arc.Arc;
16+
import io.quarkus.arc.test.ArcTestContainer;
17+
import io.quarkus.arc.test.interceptors.Simple;
18+
19+
public class FinalNonInterceptedMethodTest {
20+
21+
static final String VAL = "ping";
22+
23+
@RegisterExtension
24+
public ArcTestContainer container = new ArcTestContainer(Simple.class, SimpleBean.class,
25+
SimpleInterceptor.class);
26+
27+
@Test
28+
public void testInvocation() {
29+
SimpleBean bean = Arc.container().instance(SimpleBean.class).get();
30+
assertEquals(VAL, bean.foo());
31+
assertEquals("a" + VAL, bean.bar());
32+
}
33+
34+
@Singleton
35+
static class SimpleBean {
36+
37+
private String val;
38+
39+
@PostConstruct
40+
void init() {
41+
val = VAL;
42+
}
43+
44+
// This method is final but not intercepted = OK
45+
final String foo() {
46+
return val;
47+
}
48+
49+
@Simple
50+
String bar() {
51+
return val;
52+
}
53+
54+
}
55+
56+
@Simple
57+
@Priority(1)
58+
@Interceptor
59+
static class SimpleInterceptor {
60+
61+
@AroundInvoke
62+
Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception {
63+
return "a" + ctx.proceed();
64+
}
65+
}
66+
67+
}

0 commit comments

Comments
 (0)