From 2bbc4e48adfc697903ccc5c61504be3fd2ac1c0c Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Sun, 22 Mar 2009 02:38:44 +0000 Subject: [PATCH] + Added unit test for BeanMethod --- .../config/java/support/BeanMethod.java | 25 +-- .../config/java/support/ModelClass.java | 2 + .../config/java/support/BeanMethodTests.java | 184 ++++++++++++++++++ 3 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java index cf76944bd5..71bd420463 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java @@ -101,9 +101,7 @@ final class BeanMethod implements BeanMetadataElement { public T getRequiredAnnotation(Class annoType) { T anno = getAnnotation(annoType); - if (anno == null) - throw new IllegalStateException( - format("annotation %s not found on %s", annoType.getSimpleName(), this)); + Assert.notNull(anno, format("annotation %s not found on %s", annoType.getSimpleName(), this)); return anno; } @@ -130,6 +128,9 @@ final class BeanMethod implements BeanMetadataElement { } public Location getLocation() { + if (declaringClass == null) + throw new IllegalStateException( + "declaringClass property is null. Call setDeclaringClass() before calling getLocation()"); return new Location(declaringClass.getLocation().getResource(), getSource()); } @@ -196,24 +197,24 @@ final class BeanMethod implements BeanMetadataElement { } /** {@link Bean} methods must be non-private in order to accommodate CGLIB. */ - public class PrivateMethodError extends Problem { - public PrivateMethodError() { - super(format("method '%s' may not be private", getName()), + class PrivateMethodError extends Problem { + PrivateMethodError() { + super(format("Method '%s' may not be private; increase the method's visibility to continue", getName()), BeanMethod.this.getLocation()); } } /** {@link Bean} methods must be non-final in order to accommodate CGLIB. */ - public class FinalMethodError extends Problem { - public FinalMethodError() { - super(format("method '%s' may not be final. remove the final modifier to continue", getName()), + class FinalMethodError extends Problem { + FinalMethodError() { + super(format("Method '%s' may not be final; remove the final modifier to continue", getName()), BeanMethod.this.getLocation()); } } - public class InvalidScopedProxyDeclarationError extends Problem { - public InvalidScopedProxyDeclarationError(BeanMethod method) { - super(format("method %s contains an invalid annotation declaration: scoped proxies " + class InvalidScopedProxyDeclarationError extends Problem { + InvalidScopedProxyDeclarationError(BeanMethod method) { + super(format("Method %s contains an invalid annotation declaration: scoped proxies " + "cannot be created for singleton/prototype beans", method.getName()), BeanMethod.this.getLocation()); } diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java index c872077ba4..ca4bed9a09 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java @@ -117,6 +117,8 @@ class ModelClass implements BeanMetadataElement { } public Location getLocation() { + if(getName() == null) + throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); return new Location(new ClassPathResource(convertClassNameToResourcePath(getName())), getSource()); } diff --git a/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java new file mode 100644 index 0000000000..d20717a70c --- /dev/null +++ b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java @@ -0,0 +1,184 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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.springframework.config.java.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.config.java.StandardScopes.*; +import static org.springframework.config.java.support.MutableAnnotationUtils.*; +import static org.springframework.context.annotation.ScopedProxyMode.*; + +import java.lang.reflect.Modifier; + +import org.junit.Test; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.beans.factory.parsing.FailFastProblemReporter; +import org.springframework.beans.factory.parsing.Location; +import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.config.java.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.core.io.ClassPathResource; + + +/** + * Unit tests for {@link BeanMethod}. + * + * @author Chris Beams + */ +public class BeanMethodTests { + + private ProblemReporter problemReporter = new FailFastProblemReporter(); + private String beanName = "foo"; + private Bean beanAnno = createMutableAnnotation(Bean.class); + private ModelClass returnType = new ModelClass("FooType"); + private ConfigurationClass declaringClass = new ConfigurationClass(); + { declaringClass.setName("test.Config"); } + + @Test + public void testWellFormedMethod() { + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno); + + assertThat(beanMethod.getName(), sameInstance(beanName)); + assertThat(beanMethod.getModifiers(), equalTo(0)); + assertThat(beanMethod.getReturnType(), sameInstance(returnType)); + assertThat(beanMethod.getAnnotation(Bean.class), sameInstance(beanAnno)); + assertThat(beanMethod.getAnnotation(Override.class), nullValue()); + assertThat(beanMethod.getRequiredAnnotation(Bean.class), sameInstance(beanAnno)); + try { + beanMethod.getRequiredAnnotation(Override.class); + fail("expected IllegalArgumentException ex"); + } catch (IllegalArgumentException ex) { /* expected */ } + + // must call setDeclaringClass() before calling getLocation() + try { + beanMethod.getLocation(); + fail("expected IllegalStateException ex"); + } catch (IllegalStateException ex) { /* expected */ } + + + beanMethod.setDeclaringClass(declaringClass); + assertThat(beanMethod.getDeclaringClass(), sameInstance(declaringClass)); + + beanMethod.setSource(12); // indicating a line number + assertEquals(beanMethod.getSource(), 12); + + Location location = beanMethod.getLocation(); + assertEquals(location.getResource(), new ClassPathResource("test/Config")); + assertEquals(location.getSource(), 12); + + // should validate without throwing as this is a well-formed method + beanMethod.validate(problemReporter); + } + + @Test + public void finalMethodsAreIllegal() { + BeanMethod beanMethod = new BeanMethod(beanName, Modifier.FINAL, returnType, beanAnno); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to final bean method"); + } catch (BeanDefinitionParsingException ex) { + assertTrue(ex.getMessage().contains("remove the final modifier")); + } + } + + @Test + public void privateMethodsAreIllegal() { + BeanMethod beanMethod = new BeanMethod(beanName, Modifier.PRIVATE, returnType, beanAnno); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to private bean method"); + } catch (BeanDefinitionParsingException ex) { + assertTrue(ex.getMessage().contains("increase the method's visibility")); + } + } + + @Test + public void singletonInterfaceScopedProxiesAreIllegal() { + Scope scope = SingletonInterfaceProxy.class.getAnnotation(Scope.class); + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to singleton with scoped proxy"); + } catch (Exception ex) { + assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); + } + } + + @Test + public void singletonTargetClassScopedProxiesAreIllegal() { + Scope scope = SingletonTargetClassProxy.class.getAnnotation(Scope.class); + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to singleton with scoped proxy"); + } catch (Exception ex) { + assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); + } + } + + @Test + public void singletonsSansProxyAreLegal() { + Scope scope = SingletonNoProxy.class.getAnnotation(Scope.class); + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + beanMethod.setDeclaringClass(declaringClass); + beanMethod.validate(problemReporter); // should validate without problems - it's legal + } + + @Test + public void prototypeInterfaceScopedProxiesAreIllegal() { + Scope scope = PrototypeInterfaceProxy.class.getAnnotation(Scope.class); + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to prototype with scoped proxy"); + } catch (Exception ex) { + assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); + } + } + + @Test + public void sessionInterfaceScopedProxiesAreLegal() { + Scope scope = PrototypeInterfaceProxy.class.getAnnotation(Scope.class); + BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + beanMethod.setDeclaringClass(declaringClass); + try { + beanMethod.validate(problemReporter); + fail("should have failed due to prototype with scoped proxy"); + } catch (Exception ex) { + assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); + } + } + + @Scope(value=SINGLETON, proxyMode=INTERFACES) + private class SingletonInterfaceProxy { } + + @Scope(value=SINGLETON, proxyMode=TARGET_CLASS) + private class SingletonTargetClassProxy { } + + @Scope(value=SINGLETON, proxyMode=NO) + private class SingletonNoProxy { } + + @Scope(value=PROTOTYPE, proxyMode=INTERFACES) + private class PrototypeInterfaceProxy { } + + @Scope(value=SESSION, proxyMode=INTERFACES) + private class SessionInterfaceProxy { } +}