diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java index afa33b4df8fe8..0a7c1acd000ca 100644 --- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java @@ -85,13 +85,13 @@ public class BeanDeployment { private final boolean removeUnusedBeans; private final List> unusedExclusions; - + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { - this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, false, null); + this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), null, false, null); } BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers, - Collection resourceAnnotations, List beanRegistrars, List validators, + Collection resourceAnnotations, List beanRegistrars, BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions) { long start = System.currentTimeMillis(); this.resourceAnnotations = new HashSet<>(resourceAnnotations); @@ -145,43 +145,15 @@ public V put(Key key, V value) { } } - // Validate the bean deployment - List errors = new ArrayList<>(); - validateBeanNames(errors); - ValidationContextImpl validationContext = new ValidationContextImpl(buildContext); - for (BeanDeploymentValidator validator : validators) { - validator.validate(validationContext); - } - errors.addAll(validationContext.getErrors()); - - if (!errors.isEmpty()) { - if (errors.size() == 1) { - Throwable error = errors.get(0); - if (error instanceof DeploymentException) { - throw (DeploymentException) error; - } else { - throw new DeploymentException(errors.get(0)); - } - } else { - DeploymentException deploymentException = new DeploymentException("Multiple deployment problems occured: " + errors.stream() - .map(e -> e.getMessage()) - .collect(Collectors.toList()) - .toString()); - for (Throwable error : errors) { - deploymentException.addSuppressed(error); - } - throw deploymentException; - } - } - this.observers = observers; this.interceptorResolver = new InterceptorResolver(this); LOGGER.debugf("Bean deployment created in %s ms", System.currentTimeMillis() - start); } - private void validateBeanNames(List errors) { + private void validateBeans(List errors) { Map> namedBeans = new HashMap<>(); + for (BeanInfo bean : beans) { if (bean.getName() != null) { List named = namedBeans.get(bean.getName()); @@ -191,7 +163,9 @@ private void validateBeanNames(List errors) { } named.add(bean); } + bean.validate(errors); } + if (!namedBeans.isEmpty()) { for (Entry> entry : namedBeans.entrySet()) { if (entry.getValue() @@ -262,6 +236,39 @@ AnnotationInstance getAnnotation(AnnotationTarget target, DotName name) { boolean hasAnnotation(AnnotationTarget target, DotName name) { return annotationStore.hasAnnotation(target, name); } + + void validate(BuildContextImpl buildContext, List validators) { + long start = System.currentTimeMillis(); + // Validate the bean deployment + List errors = new ArrayList<>(); + validateBeans(errors); + ValidationContextImpl validationContext = new ValidationContextImpl(buildContext); + for (BeanDeploymentValidator validator : validators) { + validator.validate(validationContext); + } + errors.addAll(validationContext.getErrors()); + + if (!errors.isEmpty()) { + if (errors.size() == 1) { + Throwable error = errors.get(0); + if (error instanceof DeploymentException) { + throw (DeploymentException) error; + } else { + throw new DeploymentException(errors.get(0)); + } + } else { + DeploymentException deploymentException = new DeploymentException("Multiple deployment problems occured: " + errors.stream() + .map(e -> e.getMessage()) + .collect(Collectors.toList()) + .toString()); + for (Throwable error : errors) { + deploymentException.addSuppressed(error); + } + throw deploymentException; + } + } + LOGGER.debugf("Bean deployment validated in %s ms", System.currentTimeMillis() - start); + } void init() { long start = System.currentTimeMillis(); diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java index 2a83ad1f665ae..e84eb95f48618 100644 --- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java @@ -16,6 +16,7 @@ package org.jboss.protean.arc.processor; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,6 +30,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.InterceptionType; import org.jboss.jandex.AnnotationInstance; @@ -258,7 +260,7 @@ InterceptionInfo getLifecycleInterceptors(InterceptionType interceptionType) { public boolean hasLifecycleInterceptors() { return !lifecycleInterceptors.isEmpty(); } - + boolean isSubclassRequired() { return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); } @@ -322,6 +324,25 @@ Map getParams() { return params; } + void validate(List errors) { + if (isClassBean()) { + if (!target.get().asClass().hasNoArgsConstructor()) { + if (scope.isNormal()) { + errors.add(new DefinitionException("Normal scoped bean must declare a no-args constructor: " + this)); + } + } + if (Modifier.isFinal(target.get().asClass().flags())) { + if (scope.isNormal()) { + errors.add(new DefinitionException("Normal scoped bean must not be final: " + this)); + } + if (isSubclassRequired()) { + errors.add(new DefinitionException("Bean that has a bound interceptor must not be final: " + this)); + } + } + } + // TODO we should add way more validations + } + void init() { for (Injection injection : injections) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java index f7f9d3a2998c0..231dc9cadbb0b 100644 --- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java @@ -154,8 +154,9 @@ public V put(Key key, V value) { public BeanDeployment process() throws IOException { BeanDeployment beanDeployment = new BeanDeployment(new IndexWrapper(index), additionalBeanDefiningAnnotations, annotationTransformers, - resourceAnnotations, beanRegistrars, beanDeploymentValidators, buildContext, removeUnusedBeans, unusedExclusions); + resourceAnnotations, beanRegistrars, buildContext, removeUnusedBeans, unusedExclusions); beanDeployment.init(); + beanDeployment.validate(buildContext, beanDeploymentValidators); AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(name, sharedAnnotationLiterals); BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate); diff --git a/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/BoundInterceptorFinalTest.java b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/BoundInterceptorFinalTest.java new file mode 100644 index 0000000000000..8fe2ffc6e4ae2 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/BoundInterceptorFinalTest.java @@ -0,0 +1,37 @@ +package org.jboss.protean.arc.test.validation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class BoundInterceptorFinalTest { + + @Rule + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Unproxyable.class, Simple.class, SimpleInterceptor.class).shouldFail().build(); + + @Test + public void testFailure() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DeploymentException); + assertNotNull(error.getCause()); + assertTrue(error.getCause() instanceof DefinitionException); + } + + @Dependent + @Simple + static final class Unproxyable { + + void ping() { + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedConstructorTest.java b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedConstructorTest.java new file mode 100644 index 0000000000000..b2f0544d495d9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedConstructorTest.java @@ -0,0 +1,39 @@ +package org.jboss.protean.arc.test.validation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.DeploymentException; +import javax.inject.Inject; + +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class NormalScopedConstructorTest { + + @Rule + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Unproxyable.class).shouldFail().build(); + + @Test + public void testFailure() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DeploymentException); + assertNotNull(error.getCause()); + assertTrue(error.getCause() instanceof DefinitionException); + } + + @ApplicationScoped + static class Unproxyable { + + @Inject + public Unproxyable(Instance instance) { + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedFinalTest.java b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedFinalTest.java new file mode 100644 index 0000000000000..6b486c6783441 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/NormalScopedFinalTest.java @@ -0,0 +1,36 @@ +package org.jboss.protean.arc.test.validation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class NormalScopedFinalTest { + + @Rule + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Unproxyable.class).shouldFail().build(); + + @Test + public void testFailure() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertTrue(error instanceof DeploymentException); + assertNotNull(error.getCause()); + assertTrue(error.getCause() instanceof DefinitionException); + } + + @ApplicationScoped + static final class Unproxyable { + + void ping() { + } + + } + +} diff --git a/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/Simple.java b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/Simple.java new file mode 100644 index 0000000000000..a748a2bb8d849 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/Simple.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.protean.arc.test.validation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface Simple { + +} diff --git a/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/SimpleInterceptor.java b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/SimpleInterceptor.java new file mode 100644 index 0000000000000..3d592adcc87e9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/org/jboss/protean/arc/test/validation/SimpleInterceptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.protean.arc.test.validation; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.jboss.protean.arc.InvocationContextImpl; + +@Simple +@Priority(1) +@Interceptor +public class SimpleInterceptor { + + @AroundInvoke + Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } +}