Skip to content

Commit

Permalink
ArC - add basic validation for normal scoped beans
Browse files Browse the repository at this point in the history
- resolves #549
  • Loading branch information
mkouba committed Jan 18, 2019
1 parent f2a1933 commit 31ed020
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ public class BeanDeployment {

private final boolean removeUnusedBeans;
private final List<Predicate<BeanInfo>> unusedExclusions;

BeanDeployment(IndexView index, Collection<BeanDefiningAnnotation> additionalBeanDefiningAnnotations, List<AnnotationsTransformer> 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<BeanDefiningAnnotation> additionalBeanDefiningAnnotations, List<AnnotationsTransformer> annotationTransformers,
Collection<DotName> resourceAnnotations, List<BeanRegistrar> beanRegistrars, List<BeanDeploymentValidator> validators,
Collection<DotName> resourceAnnotations, List<BeanRegistrar> beanRegistrars,
BuildContextImpl buildContext, boolean removeUnusedBeans, List<Predicate<BeanInfo>> unusedExclusions) {
long start = System.currentTimeMillis();
this.resourceAnnotations = new HashSet<>(resourceAnnotations);
Expand Down Expand Up @@ -145,43 +145,15 @@ public <V> V put(Key<V> key, V value) {
}
}

// Validate the bean deployment
List<Throwable> 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<Throwable> errors) {
private void validateBeans(List<Throwable> errors) {
Map<String, List<BeanInfo>> namedBeans = new HashMap<>();

for (BeanInfo bean : beans) {
if (bean.getName() != null) {
List<BeanInfo> named = namedBeans.get(bean.getName());
Expand All @@ -191,7 +163,9 @@ private void validateBeanNames(List<Throwable> errors) {
}
named.add(bean);
}
bean.validate(errors);
}

if (!namedBeans.isEmpty()) {
for (Entry<String, List<BeanInfo>> entry : namedBeans.entrySet()) {
if (entry.getValue()
Expand Down Expand Up @@ -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<BeanDeploymentValidator> validators) {
long start = System.currentTimeMillis();
// Validate the bean deployment
List<Throwable> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -258,7 +260,7 @@ InterceptionInfo getLifecycleInterceptors(InterceptionType interceptionType) {
public boolean hasLifecycleInterceptors() {
return !lifecycleInterceptors.isEmpty();
}

boolean isSubclassRequired() {
return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY);
}
Expand Down Expand Up @@ -322,6 +324,25 @@ Map<String, Object> getParams() {
return params;
}

void validate(List<Throwable> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ public <V> V put(Key<V> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
}

}

}
Original file line number Diff line number Diff line change
@@ -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<String> instance) {
}

}

}
Original file line number Diff line number Diff line change
@@ -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() {
}

}

}
Original file line number Diff line number Diff line change
@@ -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 {

}
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 31ed020

Please sign in to comment.