From 431094e6642526267769bc6fa8e3f2bfe26e4bb3 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 16 Apr 2014 21:51:33 -0700 Subject: [PATCH] Add @GuiceModules with includes and excludes --- .../guice/GuiceModuleMetadataRegistrar.java | 179 ++++++++++++++++++ .../springframework/guice/GuiceModules.java | 45 +++++ .../guice/GuiceModulesTests.java | 96 ++++++++++ 3 files changed, 320 insertions(+) create mode 100644 src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java create mode 100644 src/main/java/org/springframework/guice/GuiceModules.java create mode 100644 src/test/java/org/springframework/guice/GuiceModulesTests.java diff --git a/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java b/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java new file mode 100644 index 0000000..0f3bc42 --- /dev/null +++ b/src/main/java/org/springframework/guice/GuiceModuleMetadataRegistrar.java @@ -0,0 +1,179 @@ +/* + * Copyright 2012-2013 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.guice; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AspectJTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.Assert; + +/** + * @author Dave Syer + * + */ +public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistrar, + ResourceLoaderAware { + + private ResourceLoader resourceLoader = new DefaultResourceLoader(); + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata annotation, + BeanDefinitionRegistry registry) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(GuiceModuleMetadataFactory.class); + builder.addPropertyValue("includeFilters", + parseFilters(annotation, "includeFilters")); + builder.addPropertyValue("excludeFilters", + parseFilters(annotation, "excludeFilters")); + registry.registerBeanDefinition(GuiceModules.class.getName(), + builder.getBeanDefinition()); + } + + public static class GuiceModuleMetadataFactory implements + FactoryBean { + + private Collection includeFilters; + + private Collection excludeFilters; + + public void setIncludeFilters(Collection includeFilters) { + this.includeFilters = includeFilters; + } + + public void setExcludeFilters(Collection excludeFilters) { + this.excludeFilters = excludeFilters; + } + + @Override + public GuiceModuleMetadata getObject() throws Exception { + return new GuiceModuleMetadata() + .include( + includeFilters.toArray(new TypeFilter[includeFilters.size()])) + .exclude( + excludeFilters.toArray(new TypeFilter[excludeFilters.size()])); + } + + @Override + public Class getObjectType() { + return GuiceModuleMetadata.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + + private Set parseFilters(AnnotationMetadata annotation, + String attributeName) { + + Set result = new HashSet(); + AnnotationAttributes attributes = new AnnotationAttributes( + annotation.getAnnotationAttributes(GuiceModules.class.getName())); + AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName); + + for (AnnotationAttributes filter : filters) { + result.addAll(typeFiltersFor(filter)); + } + + return result; + } + + private List typeFiltersFor(AnnotationAttributes filterAttributes) { + + List typeFilters = new ArrayList(); + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : filterAttributes.getClassArray("value")) { + switch (filterType) { + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, + "An error occured when processing a @ComponentScan " + + "ANNOTATION type filter: "); + @SuppressWarnings("unchecked") + Class annoClass = (Class) filterClass; + typeFilters.add(new AnnotationTypeFilter(annoClass)); + break; + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, + "An error occured when processing a @ComponentScan " + + "CUSTOM type filter: "); + typeFilters + .add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); + break; + default: + throw new IllegalArgumentException("Unknown filter type " + filterType); + } + } + + for (String expression : getPatterns(filterAttributes)) { + + String rawName = filterType.toString(); + + if ("REGEX".equals(rawName)) { + typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression))); + } else if ("ASPECTJ".equals(rawName)) { + typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader + .getClassLoader())); + } else { + throw new IllegalArgumentException("Unknown filter type " + filterType); + } + } + + return typeFilters; + } + + private String[] getPatterns(AnnotationAttributes filterAttributes) { + + try { + return filterAttributes.getStringArray("pattern"); + } catch (IllegalArgumentException o_O) { + return new String[0]; + } + } + +} diff --git a/src/main/java/org/springframework/guice/GuiceModules.java b/src/main/java/org/springframework/guice/GuiceModules.java new file mode 100644 index 0000000..2e3e670 --- /dev/null +++ b/src/main/java/org/springframework/guice/GuiceModules.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2014 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.guice; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Import; + +/** + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(GuiceModuleMetadataRegistrar.class) +public @interface GuiceModules { + + /** + * Specifies which types are eligible for inclusion in Guice module + */ + Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for inclusion in Guice module. + */ + Filter[] excludeFilters() default {}; + +} diff --git a/src/test/java/org/springframework/guice/GuiceModulesTests.java b/src/test/java/org/springframework/guice/GuiceModulesTests.java new file mode 100644 index 0000000..85b11e9 --- /dev/null +++ b/src/test/java/org/springframework/guice/GuiceModulesTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013-2014 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.guice; + +import static org.junit.Assert.assertNull; + +import javax.inject.Inject; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; + +import com.google.inject.ConfigurationException; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * @author Dave Syer + * + */ +public class GuiceModulesTests { + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void includes() throws Exception { + Injector injector = createInjector(TestConfig.class, MetadataIncludesConfig.class); + expected.expect(ConfigurationException.class); + assertNull(injector.getBinding(Service.class)); + } + + @Test + public void excludes() throws Exception { + Injector injector = createInjector(TestConfig.class, MetadataExcludesConfig.class); + expected.expect(ConfigurationException.class); + assertNull(injector.getInstance(Service.class)); + } + + private Injector createInjector(Class... config) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(config); + context.refresh(); + Injector injector = Guice.createInjector(new SpringModule(context)); + return injector; + } + + interface Service { + } + + protected static class MyService implements Service { + } + + public static class Foo { + + @Inject + public Foo(Service service) { + } + + } + + @Configuration + @GuiceModules(excludeFilters=@Filter(type=FilterType.REGEX, pattern=".*")) + protected static class MetadataExcludesConfig { + } + + // TODO: allow include Service.class as well + @Configuration + @GuiceModules(includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=MyService.class)) + protected static class MetadataIncludesConfig { + } + + @Configuration + public static class TestConfig { + @Bean + public Service service() { + return new MyService(); + } + } +}