From b68c1f450cfcfba3247b6b1c206bf616c4d53371 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 17 Feb 2016 09:08:16 -0800 Subject: [PATCH] SGF-469 - Add support for CDI. (cherry picked from commit 34da7ec91044c9f01647d444ccbe12ed890b4cc6) Signed-off-by: John Blum --- build.gradle | 17 + gradle.properties | 5 +- pom.xml | 31 +- .../repository/cdi/GemfireRepositoryBean.java | 155 ++++++++ .../cdi/GemfireRepositoryExtension.java | 134 +++++++ .../support/GemfireRepositoryFactoryBean.java | 3 +- .../javax.enterprise.inject.spi.Extension | 1 + .../cdi/CdiExtensionIntegrationTest.java | 109 ++++++ .../cdi/CustomPersonRepository.java | 31 ++ .../cdi/GemfireCacheRegionProducer.java | 69 ++++ .../cdi/GemfireRepositoryBeanTest.java | 344 ++++++++++++++++++ .../cdi/GemfireRepositoryExtensionTest.java | 191 ++++++++++ .../repository/cdi/RepositoryClient.java | 62 ++++ .../cdi/SamplePersonRepository.java | 38 ++ .../cdi/SamplePersonRepositoryImpl.java | 34 ++ src/test/resources/META-INF/beans.xml | 5 + template.mf | 12 +- 17 files changed, 1231 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtension.java create mode 100644 src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/CdiExtensionIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/CustomPersonRepository.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireCacheRegionProducer.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBeanTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtensionTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/RepositoryClient.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java create mode 100644 src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepositoryImpl.java create mode 100644 src/test/resources/META-INF/beans.xml diff --git a/build.gradle b/build.gradle index 0f50a274..85e4b6d0 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,17 @@ if (project.hasProperty('platformVersion')) { } } +sourceSets { + main { + output.resourcesDir = 'build/classes/main' + output.classesDir = 'build/classes/main' + } + test { + output.resourcesDir = 'build/classes/test' + output.classesDir = 'build/classes/test' + } +} + [compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:-serial"] // Common dependencies @@ -74,6 +85,7 @@ dependencies { exclude group: "com.fasterxml.jackson.core", module: "jackson-annotations" exclude group: "com.fasterxml.jackson.core", module: "jackson-core" exclude group: "com.fasterxml.jackson.core", module: "jackson-databind" + exclude group: "org.apache.hbase", module: "hbase" exclude group: "org.slf4j", module: "slf4j-api" exclude group: "org.springframework", module: "spring-aop" exclude group: "org.springframework", module: "spring-beans" @@ -90,6 +102,7 @@ dependencies { optional("com.google.code.findbugs:annotations:2.0.2") runtime("antlr:antlr:$antlrVersion") + optional "javax.enterprise:cdi-api:$cdiVersion" compile "org.aspectj:aspectjweaver:$aspectjVersion" compile "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion" compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" @@ -103,11 +116,15 @@ dependencies { testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" testCompile "org.mockito:mockito-core:$mockitoVersion" testCompile "edu.umd.cs.mtc:multithreadedtc:$multiThreadedtcVersion" + testCompile "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion" testCompile "javax.annotation:jsr250-api:1.0", optional + testRuntime "javax.el:el-api:$cdiVersion" + testRuntime "javax.servlet:servlet-api:$servletApiVersion" testRuntime "log4j:log4j:$log4jVersion" testRuntime "org.apache.derby:derbyLocale_zh_TW:10.9.1.0" testRuntime "org.slf4j:slf4j-log4j12:$slf4jVersion" + testRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" sharedResources "org.springframework.data.build:spring-data-build-resources:$springDataBuildVersion@zip" } diff --git a/gradle.properties b/gradle.properties index 59b18f77..b7990378 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,15 @@ antlrVersion=2.7.7 aspectjVersion=1.8.5 -gemfireVersion=1.0.0-incubating-SNAPSHOT +cdiVersion=1.0 +gemfireVersion=1.0.0-incubating.M2-SNAPSHOT hamcrestVersion=1.3 jacksonVersion=2.6.0 junitVersion=4.12 log4jVersion=1.2.17 mockitoVersion=1.10.19 multiThreadedtcVersion=1.01 +openwebbeansVersion=1.2.8 +servletApiVersion=2.5 slf4jVersion=1.7.12 spring.range="[4.0.0, 5.0.0)" springVersion=4.1.9.RELEASE diff --git a/pom.xml b/pom.xml index 73fd79c5..9cc2c5e6 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,8 @@ 2.7.7 1.0.0-incubating-SNAPSHOT 1.01 - 1.11.2.RELEASE + 2.5 + 1.12.0.BUILD-SNAPSHOT @@ -61,6 +62,14 @@ ${springdata.commons} + + javax.enterprise + cdi-api + ${cdi} + provided + true + + org.apache.geode gemfire-core @@ -192,6 +201,26 @@ + + javax.el + el-api + ${cdi} + test + + + javax.servlet + servlet-api + ${servlet-api.version} + test + + + + org.apache.openwebbeans.test + cditest-owb + ${webbeans} + test + + org.apache.derby derbyLocale_zh_TW diff --git a/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBean.java b/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBean.java new file mode 100644 index 00000000..4a52f876 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBean.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory; +import org.springframework.data.repository.cdi.CdiRepositoryBean; +import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; + +import com.gemstone.gemfire.cache.Region; + +/** + * A CDI-based bean that represents a GemFire Repository. + * + * @author John Blum + * @param class type of the Repository. + * @see javax.enterprise.context.spi.CreationalContext + * @see javax.enterprise.inject.spi.Bean + * @see javax.enterprise.inject.spi.BeanManager + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory + * @see org.springframework.data.repository.cdi.CdiRepositoryBean + * @see org.springframework.data.repository.config.CustomRepositoryImplementationDetector + * @see com.gemstone.gemfire.cache.Region + * @since 1.8.0 + */ +class GemfireRepositoryBean extends CdiRepositoryBean { + + static final GemfireMappingContext DEFAULT_GEMFIRE_MAPPING_CONTEXT = new GemfireMappingContext(); + + private final Bean gemfireMappingContextBean; + + private final BeanManager beanManager; + + private final Set> regionBeans; + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + GemfireRepositoryBean(BeanManager beanManager, Class repositoryType, Set qualifiers, + CustomRepositoryImplementationDetector detector, Bean gemfireMappingContextBean, + Set> regionBeans) { + + super(qualifiers, repositoryType, beanManager, detector); + + this.beanManager = beanManager; + this.gemfireMappingContextBean = gemfireMappingContextBean; + this.regionBeans = regionBeans; + } + + /** + * Returns an instance of the given {@link Bean} from the container. + * + * @param the actual class type of the {@link Bean}. + * @param bean the {@link Bean} defining the instance to create. + * @param type the expected component type of the instance created from the {@link Bean}. + * @return an instance of the given {@link Bean}. + * @see javax.enterprise.inject.spi.BeanManager#getReference(Bean, Type, CreationalContext) + * @see javax.enterprise.inject.spi.Bean + * @see java.lang.reflect.Type + */ + @SuppressWarnings("unchecked") + protected S getDependencyInstance(Bean bean, Type type) { + return (S) beanManager.getReference(bean, type, beanManager.createCreationalContext(bean)); + } + + /** + * Resolves the desired, actual component type from the {@link Bean} in which the instance is created. + * + * @param the class type of the component. + * @param bean the {@link Bean} from which the types are evaluated and an instance is created. + * @param targetType the desired class type of the component. + * @return a resolved component {@link Type} of the {@link Bean}instance. + * @throws IllegalStateException if the desired class type cannot be resolved. + * @see javax.enterprise.inject.spi.Bean#getTypes() + * @see java.lang.Class + */ + @SuppressWarnings("unchecked") + protected Type resolveType(Bean bean, Class targetType) { + for (Type type : bean.getTypes()) { + Type assignableType = (type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type); + + if (assignableType instanceof Class && targetType.isAssignableFrom((Class) assignableType)) { + return type; + } + } + + throw new IllegalStateException(String.format( + "unable to resolve bean instance of type [%1$s] from bean definition [%2$s]", + targetType, bean)); + } + + /* (non-Javadoc) */ + Iterable> resolveGemfireRegions() { + Set> regions = new HashSet>(regionBeans.size()); + + for (Bean regionBean : regionBeans) { + regions.add(getDependencyInstance(regionBean, resolveType(regionBean, Region.class))); + } + + return regions; + } + + /* (non-Javadoc) */ + GemfireMappingContext resolveGemfireMappingContext() { + return (gemfireMappingContextBean != null + ? getDependencyInstance(gemfireMappingContextBean, GemfireMappingContext.class) + : DEFAULT_GEMFIRE_MAPPING_CONTEXT); + } + + /* (non-Javadoc) */ + GemfireRepositoryFactory newGemfireRepositoryFactory() { + return new GemfireRepositoryFactory(resolveGemfireRegions(), resolveGemfireMappingContext()); + } + + /** + * Creates an instance of the given Repository type as a bean instance in the CDI container. + * + * @param creationalContext operations used by the {@link javax.enterprise.context.spi.Contextual} implementation + * during creation of the bean instance. + * @param repositoryType the actual class type of the SD (GemFire) Repository. + * @param customImplementation the supporting custom Repository implementing class. + * @return a factory used to create instance of {@link org.springframework.data.gemfire.repository.GemfireRepository}. + * @see javax.enterprise.context.spi.Contextual#create(javax.enterprise.context.spi.CreationalContext) + * @see #newGemfireRepositoryFactory() + */ + @Override + protected T create(CreationalContext creationalContext, Class repositoryType, Object customImplementation) { + return newGemfireRepositoryFactory().getRepository(repositoryType, customImplementation); + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtension.java b/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtension.java new file mode 100644 index 00000000..3978ccff --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtension.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.ProcessBean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.repository.cdi.CdiRepositoryBean; +import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; + +import com.gemstone.gemfire.cache.Region; + +/** + * The GemfireRepositoryExtension class... + * + * @author John Blum + * @see org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport + * @since 1.8.0 + */ +@SuppressWarnings("unused") +public class GemfireRepositoryExtension extends CdiRepositoryExtensionSupport { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + final Map, Bean> mappingContexts = + new HashMap, Bean>(); + + final Set> regionBeans = new HashSet>(); + + /* (non-Javadoc) */ + public GemfireRepositoryExtension() { + logger.info("Activating CDI extension for Spring Data GemFire Repositories"); + } + + /** + * Implementation of an observer that captures GemFire Region beans defined in the CDI container, storing them + * along with any defined GemfireMappingContexts for later construction of the Repository beans. + * + * @param class type of the bean instance. + * @param processBean annotated type as defined by CDI. + * @see javax.enterprise.inject.spi.ProcessBean + * @see javax.enterprise.event.Observes + */ + @SuppressWarnings("unchecked") + void processBean(@Observes ProcessBean processBean) { + Bean bean = processBean.getBean(); + + for (Type type : bean.getTypes()) { + Type resolvedType = (type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type); + + if (resolvedType instanceof Class) { + Class classType = (Class) resolvedType; + + if (Region.class.isAssignableFrom(classType)) { + logger.debug("Found Region bean with name {}", bean.getName()); + regionBeans.add((Bean) bean); + } + else if (GemfireMappingContext.class.isAssignableFrom(classType)) { + logger.debug("Discovered {} bean with types {} having qualifiers {}", + GemfireMappingContext.class.getName(), bean.getTypes(), bean.getQualifiers()); + mappingContexts.put(bean.getQualifiers(), (Bean) bean); + } + } + } + } + + /** + * Implementation of an observer that registers beans in the CDI container for the detected Spring Data + * Repositories. + * + * Repository beans are associated to the appropriate GemfireMappingContexts based on their qualifiers. + * + * @param beanManager the BeanManager instance. + * @see javax.enterprise.inject.spi.AfterBeanDiscovery + * @see javax.enterprise.inject.spi.BeanManager + * @see javax.enterprise.event.Observes + */ + void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { + for (Map.Entry, Set> entry : getRepositoryTypes()) { + Class repositoryType = entry.getKey(); + Set qualifiers = entry.getValue(); + + // Create the bean representing the Repository. + CdiRepositoryBean repositoryBean = createRepositoryBean(beanManager, repositoryType, qualifiers); + + logger.info("Registering bean for '{}' with qualifiers {}.", repositoryType.getName(), qualifiers); + + // Register the bean with the extension and the container. + registerBean(repositoryBean); + afterBeanDiscovery.addBean(repositoryBean); + } + } + + /* (non-Javadoc) */ + CdiRepositoryBean createRepositoryBean(BeanManager beanManager, Class repositoryType, + Set qualifiers) { + + // Determine the GemfireMappingContext bean that matches the qualifiers of the Repository. + Bean gemfireMappingContextBean = mappingContexts.get(qualifiers); + + // Construct and return a GemFire Repository bean. + return new GemfireRepositoryBean(beanManager, repositoryType, qualifiers, getCustomImplementationDetector(), + gemfireMappingContextBean, regionBeans); + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java index e9ce648e..98634c88 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java @@ -55,6 +55,7 @@ public class GemfireRepositoryFactoryBean, S, ID ext extends RepositoryFactoryBeanSupport implements ApplicationContextAware { private Iterable> regions; + private MappingContext, GemfirePersistentProperty> context; /** @@ -67,7 +68,6 @@ public class GemfireRepositoryFactoryBean, S, ID ext @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - Collection regions = applicationContext.getBeansOfType(Region.class).values(); this.regions = (Iterable) Collections.unmodifiableCollection(regions); } @@ -80,7 +80,6 @@ public class GemfireRepositoryFactoryBean, S, ID ext * @see org.springframework.data.mapping.context.MappingContext */ public void setGemfireMappingContext(MappingContext, GemfirePersistentProperty> mappingContext) { - setMappingContext(mappingContext); this.context = mappingContext; } diff --git a/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000..32a846a9 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.springframework.data.gemfire.repository.cdi.GemfireRepositoryExtension diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/CdiExtensionIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/CdiExtensionIntegrationTest.java new file mode 100644 index 00000000..a8889dc9 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/CdiExtensionIntegrationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.apache.webbeans.cditest.CdiTestContainer; +import org.apache.webbeans.cditest.CdiTestContainerLoader; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.data.gemfire.repository.sample.Person; + +import com.gemstone.gemfire.cache.CacheClosedException; +import com.gemstone.gemfire.cache.CacheFactory; + +/** + * The CdiExtensionIntegrationTest class... + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.data.gemfire.repository.cdi.GemfireRepositoryBean + * @see org.springframework.data.gemfire.repository.cdi.GemfireRepositoryExtension + * @see org.apache.webbeans.cditest.CdiTestContainer + * @see org.apache.webbeans.cditest.CdiTestContainerLoader + * @since 1.8.0 + */ +public class CdiExtensionIntegrationTest { + + static CdiTestContainer container; + + @BeforeClass + public static void setUp() throws Exception { + container = CdiTestContainerLoader.getCdiContainer(); + container.bootContainer(); + } + + @AfterClass + public static void tearDown() throws Exception { + container.shutdownContainer(); + closeGemfireCache(); + } + + private static void closeGemfireCache() { + try { + CacheFactory.getAnyInstance().close(); + } + catch (CacheClosedException ignore) { + } + } + + protected void assertIsExpectedPerson(Person actual, Person expected) { + assertThat(actual.getId(), is(equalTo(expected.getId()))); + assertThat(actual.getFirstname(), is(equalTo(expected.getFirstname()))); + assertThat(actual.getLastname(), is(equalTo(expected.getLastname()))); + } + + @Test + public void bootstrapsRepositoryCorrectly() { + RepositoryClient repositoryClient = container.getInstance(RepositoryClient.class); + + assertThat(repositoryClient.getPersonRepository(), is(notNullValue())); + + Person expectedJonDoe = repositoryClient.newPerson("Jon", "Doe"); + + assertThat(expectedJonDoe, is(notNullValue())); + assertThat(expectedJonDoe.getId(), is(greaterThan(0l))); + assertThat(expectedJonDoe.getName(), is(equalTo("Jon Doe"))); + + Person savedJonDoe = repositoryClient.save(expectedJonDoe); + + assertIsExpectedPerson(savedJonDoe, expectedJonDoe); + + Person foundJonDoe = repositoryClient.find(expectedJonDoe.getId()); + + assertIsExpectedPerson(foundJonDoe, expectedJonDoe); + + assertThat(repositoryClient.delete(expectedJonDoe), is(true)); + assertThat(repositoryClient.find(expectedJonDoe.getId()), is(nullValue())); + } + + @Test + public void returnOneFromCustomImplementation() { + RepositoryClient repositoryClient = container.getInstance(RepositoryClient.class); + + assertThat(repositoryClient.getPersonRepository().returnOne(), is(equalTo(1))); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/CustomPersonRepository.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/CustomPersonRepository.java new file mode 100644 index 00000000..70b2ee32 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/CustomPersonRepository.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +/** + * The CustomPersonRepository interface is an Spring Data Repository extension type specifying additional, "custom" + * data access operations on people. + * + * @author John Blum + * @since 1.8.0 + */ +public interface CustomPersonRepository { + + int returnOne(); + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireCacheRegionProducer.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireCacheRegionProducer.java new file mode 100644 index 00000000..dc78e78a --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireCacheRegionProducer.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheFactory; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionFactory; +import com.gemstone.gemfire.cache.RegionShortcut; + +/** + * The GemfireCacheRegionProducer class is an application scoped CDI context bean that is responsible + * for creating the GemFire Cache "People" Region used to store {@link Person} instances. + * + * @author John Blum + * @see javax.enterprise.context.ApplicationScoped + * @see javax.enterprise.inject.Produces + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.CacheFactory + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.RegionFactory + * @since 1.8.0 + */ +@SuppressWarnings("unused") +public class GemfireCacheRegionProducer { + + @Produces + @ApplicationScoped + public Region createPeopleRegion() { + Cache gemfireCache = new CacheFactory() + .set("name", "SpringDataGemFireCdiTest") + .set("mcast-port", "0") + .set("log-level", "warning") + .create(); + + RegionFactory peopleRegionFactory = gemfireCache.createRegionFactory(RegionShortcut.REPLICATE); + + peopleRegionFactory.setKeyConstraint(Long.class); + peopleRegionFactory.setValueConstraint(Person.class); + + Region peopleRegion = peopleRegionFactory.create("People"); + + Assert.notNull(peopleRegion); + + return peopleRegion; + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBeanTest.java new file mode 100644 index 00000000..fcd7b3db --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryBeanTest.java @@ -0,0 +1,344 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.data.gemfire.GemfireAccessor; +import org.springframework.data.gemfire.TestUtils; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.repository.GemfireRepository; +import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory; +import org.springframework.data.gemfire.repository.support.SimpleGemfireRepository; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; + +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; + +/** + * The GemfireRepositoryBeanTest class is a test suite of test cases testing the contract and functionality + * of the GemfireRepositoryBean class. + * + * @author John Blum + * @see org.junit.Rule + * @see org.junit.Test + * @see org.junit.rules.ExpectedException + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.data.gemfire.repository.cdi.GemfireRepositoryBean + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("unchecked") +public class GemfireRepositoryBeanTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Mock + private BeanManager mockBeanManager; + + protected T[] asArray(T... array) { + return array; + } + + protected Set asSet(T... array) { + return new HashSet(Arrays.asList(array)); + } + + protected Set asSet(Iterable collection) { + Set set = new HashSet(); + + for (T element : collection) { + set.add(element); + } + + return set; + } + + @Test + public void getDependencyInstanceGetsReference() { + Bean mockRegionBean = mock(Bean.class); + CreationalContext mockCreationalContext = mock(CreationalContext.class); + Region mockRegion = mock(Region.class); + + when(mockBeanManager.createCreationalContext(eq(mockRegionBean))).thenReturn(mockCreationalContext); + when(mockBeanManager.getReference(eq(mockRegionBean), eq(Region.class), eq(mockCreationalContext))) + .thenReturn(mockRegion); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, null); + + assertThat(repositoryBean.getDependencyInstance(mockRegionBean, Region.class), is(equalTo(mockRegion))); + + verify(mockBeanManager, times(1)).createCreationalContext(eq(mockRegionBean)); + verify(mockBeanManager, times(1)).getReference(eq(mockRegionBean), eq(Region.class), eq(mockCreationalContext)); + } + + @Test + public void resolveGemfireMappingContextUsesDefault() { + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, null); + + assertThat(repositoryBean.resolveGemfireMappingContext(), + is(equalTo(GemfireRepositoryBean.DEFAULT_GEMFIRE_MAPPING_CONTEXT))); + } + + @Test + public void resolveGemfireMappingContextUsesQualifiedMappingContext() { + Bean mockMappingContextBean = mock(Bean.class); + CreationalContext mockCreationalContext = mock(CreationalContext.class); + GemfireMappingContext expectedGemfireMappingContext = new GemfireMappingContext(); + + when(mockBeanManager.createCreationalContext(eq(mockMappingContextBean))).thenReturn(mockCreationalContext); + when(mockBeanManager.getReference(eq(mockMappingContextBean), eq(GemfireMappingContext.class), + eq(mockCreationalContext))).thenReturn(expectedGemfireMappingContext); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, + mockMappingContextBean, null); + + GemfireMappingContext actualGemfireMappingContext = repositoryBean.resolveGemfireMappingContext(); + + assertThat(actualGemfireMappingContext, is(equalTo(expectedGemfireMappingContext))); + + verify(mockBeanManager, times(1)).createCreationalContext(eq(mockMappingContextBean)); + verify(mockBeanManager, times(1)).getReference(eq(mockMappingContextBean), eq(GemfireMappingContext.class), + eq(mockCreationalContext)); + } + + @Test + public void resolveGemfireRegions() { + Region mockRegionOne = mock(Region.class); + Region mockRegionTwo = mock(Region.class); + + CreationalContext> mockCreationalContext = mock(CreationalContext.class); + + Bean mockRegionBeanOne = mock(Bean.class); + Bean mockRegionBeanTwo = mock(Bean.class); + + when(mockRegionBeanOne.getTypes()).thenReturn(asSet((Type) Region.class)); + when(mockRegionBeanTwo.getTypes()).thenReturn(asSet((Type) Region.class)); + when(mockBeanManager.createCreationalContext(any(Bean.class))).thenReturn(mockCreationalContext); + when(mockBeanManager.getReference(eq(mockRegionBeanOne), eq(Region.class), eq(mockCreationalContext))) + .thenReturn(mockRegionOne); + when(mockBeanManager.getReference(eq(mockRegionBeanTwo), eq(Region.class), eq(mockCreationalContext))) + .thenReturn(mockRegionTwo); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean(mockBeanManager, + PersonRepository.class, Collections.emptySet(), null, null, asSet(mockRegionBeanOne, mockRegionBeanTwo)); + + Iterable regions = repositoryBean.resolveGemfireRegions(); + + assertThat(regions, is(notNullValue())); + assertThat(asSet(regions).containsAll(asSet(mockRegionOne, mockRegionTwo)), is(true)); + + verify(mockRegionBeanOne, times(1)).getTypes(); + verify(mockRegionBeanTwo, times(1)).getTypes(); + verify(mockBeanManager, times(1)).createCreationalContext(eq(mockRegionBeanOne)); + verify(mockBeanManager, times(1)).createCreationalContext(eq(mockRegionBeanTwo)); + verify(mockBeanManager, times(1)).getReference(eq(mockRegionBeanOne), eq(Region.class), + eq(mockCreationalContext)); + verify(mockBeanManager, times(1)).getReference(eq(mockRegionBeanTwo), eq(Region.class), + eq(mockCreationalContext)); + } + + @Test + public void resolveTypeFindsTargetComponentType() { + Bean mockBean = mock(Bean.class); + + when(mockBean.getTypes()).thenReturn( + asSet((Type) Object.class, Map.class, ConcurrentMap.class, Region.class)); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, null); + + assertThat(repositoryBean.resolveType(mockBean, Region.class), is(equalTo((Type) Region.class))); + assertThat(repositoryBean.resolveType(mockBean, Map.class), isIn(asArray((Type) Map.class, + ConcurrentMap.class, Region.class))); + + verify(mockBean, times(2)).getTypes(); + } + + @Test + public void resolveTypeWithParameterizedType() { + Bean mockBean = mock(Bean.class); + Map parameterizedTypeMap = Collections.emptyMap(); + ParameterizedType mockParameterizedType = mock(ParameterizedType.class); + + assertThat(parameterizedTypeMap.getClass(), is(instanceOf(Type.class))); + assertThat(parameterizedTypeMap.getClass().getGenericSuperclass(), is(instanceOf(ParameterizedType.class))); + assertThat(parameterizedTypeMap.getClass().getTypeParameters().length, is(equalTo(2))); + + when(mockBean.getTypes()).thenReturn(asSet((Type) mockParameterizedType)); + when(mockParameterizedType.getRawType()).thenReturn(parameterizedTypeMap.getClass()); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, null); + + assertThat(repositoryBean.resolveType(mockBean, Map.class), is(equalTo((Type) mockParameterizedType))); + + verify(mockBean, times(1)).getTypes(); + verify(mockParameterizedType, times(1)).getRawType(); + } + + @Test + public void resolveTypeWithUnresolvableType() { + Bean mockBean = mock(Bean.class); + + when(mockBean.getTypes()).thenReturn(asSet((Type) Map.class, Object.class)); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, null); + + try { + expectedException.expect(IllegalStateException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage(is(equalTo(String.format( + "unable to resolve bean instance of type [%1$s] from bean definition [%2$s]", + Region.class, mockBean)))); + + repositoryBean.resolveType(mockBean, Region.class); + } + finally { + verify(mockBean, times(1)).getTypes(); + } + } + + @Test + // IntegrationTest + public void createGemfireRepositoryInstanceSuccessfully() throws Exception { + Bean mockRegionBean = mock(Bean.class); + + CreationalContext> mockCreationalContext = mock(CreationalContext.class); + + final Region mockRegion = mock(Region.class); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + when(mockRegion.getName()).thenReturn("Person"); + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegionAttributes.getKeyConstraint()).thenReturn(Long.class); + when(mockRegionBean.getTypes()).thenReturn(asSet((Type) Region.class)); + when(mockBeanManager.createCreationalContext(any(Bean.class))).thenReturn(mockCreationalContext); + when(mockBeanManager.getReference(eq(mockRegionBean), eq(Region.class), eq(mockCreationalContext))) + .thenReturn(mockRegion); + + final AtomicBoolean repositoryProxyPostProcessed = new AtomicBoolean(false); + + GemfireRepositoryBean repositoryBean = new GemfireRepositoryBean( + mockBeanManager, PersonRepository.class, Collections.emptySet(), null, null, + asSet(mockRegionBean)) + { + @Override + GemfireRepositoryFactory newGemfireRepositoryFactory() { + GemfireRepositoryFactory gemfireRepositoryFactory = super.newGemfireRepositoryFactory(); + + gemfireRepositoryFactory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() { + public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { + try { + assertThat((Class) repositoryInformation.getRepositoryInterface(), + is(equalTo(PersonRepository.class))); + assertThat((Class) repositoryInformation.getRepositoryBaseClass(), + is(equalTo(SimpleGemfireRepository.class))); + assertThat((Class) repositoryInformation.getDomainType(), is(equalTo(Person.class))); + assertThat((Class) repositoryInformation.getIdType(), is(equalTo(Long.class))); + assertThat((Class) factory.getTargetClass(), + is(equalTo(SimpleGemfireRepository.class))); + + Object gemfireRepository = factory.getTargetSource().getTarget(); + + GemfireAccessor gemfireAccessor = TestUtils.readField("template", gemfireRepository); + + assertThat(gemfireAccessor, is(notNullValue())); + assertThat(gemfireAccessor.getRegion(), is(equalTo(mockRegion))); + + repositoryProxyPostProcessed.set(true); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + return gemfireRepositoryFactory; + } + }; + + GemfireRepository gemfireRepository = repositoryBean.create(null, PersonRepository.class, null); + + assertThat(gemfireRepository, is(notNullValue())); + assertThat(repositoryProxyPostProcessed.get(), is(true)); + + verify(mockBeanManager, times(1)).createCreationalContext(eq(mockRegionBean)); + verify(mockBeanManager, times(1)).getReference(eq(mockRegionBean), eq(Region.class), + eq(mockCreationalContext)); + verify(mockRegionBean, times(1)).getTypes(); + verify(mockRegion, times(1)).getName(); + verify(mockRegion, times(1)).getAttributes(); + verify(mockRegionAttributes, times(1)).getKeyConstraint(); + } + + class TestMap extends AbstractMap { + @Override public Set> entrySet() { + return Collections.emptySet(); + } + } + + class Person {} + + interface PersonRepository extends GemfireRepository { + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtensionTest.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtensionTest.java new file mode 100644 index 00000000..fef05409 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/GemfireRepositoryExtensionTest.java @@ -0,0 +1,191 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.ProcessBean; +import javax.inject.Qualifier; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.repository.GemfireRepository; + +import com.gemstone.gemfire.cache.Region; + +/** + * The GemfireRepositoryExtensionTest class is a test suite of unit tests testing the contract and proper functionality + * of the {@link GemfireRepositoryExtension} class in a Java EE CDI context. + * + * @author John Blum + * @see javax.enterprise.inject.spi.Bean + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see org.springframework.data.gemfire.repository.GemfireRepository + * @see org.springframework.data.gemfire.repository.cdi.GemfireRepositoryExtension + * @since 1.0.0 + */ +@SuppressWarnings("unchecked") +public class GemfireRepositoryExtensionTest { + + private GemfireRepositoryExtension repositoryExtension; + + @Before + public void setup() { + repositoryExtension = new GemfireRepositoryExtension(); + } + + protected Set asSet(T... array) { + return new HashSet(Arrays.asList(array)); + } + + protected Annotation mockAnnotation(Class annotationType) { + Annotation mockAnnotation = mock(Annotation.class); + when(mockAnnotation.annotationType()).thenReturn(annotationType); + return mockAnnotation; + } + + @Test + public void processBeanIdentifiesAndProcessesRegionBeanCorrectly() { + ProcessBean mockProcessBean = mock(ProcessBean.class); + Bean mockBean = mock(Bean.class); + + when(mockProcessBean.getBean()).thenReturn(mockBean); + when(mockBean.getTypes()).thenReturn(Collections.singleton((Type) Region.class)); + + assertThat(repositoryExtension.regionBeans.isEmpty(), is(true)); + + repositoryExtension.processBean(mockProcessBean); + + assertThat(repositoryExtension.regionBeans.contains(mockBean), is(true)); + + verify(mockProcessBean, times(1)).getBean(); + verify(mockBean, times(1)).getTypes(); + } + + @Test + public void processBeanIdentifiesAndProcessesGemfireMappingContextBeanCorrectly() { + ProcessBean mockProcessBean = mock(ProcessBean.class); + Bean mockBean = mock(Bean.class); + + Set expectedQualifiers = asSet(mockAnnotation(SpringDataRepo.class), + mockAnnotation(GemfireRepo.class)); + + when(mockProcessBean.getBean()).thenReturn(mockBean); + when(mockBean.getTypes()).thenReturn(Collections.singleton((Type) GemfireMappingContext.class)); + when(mockBean.getQualifiers()).thenReturn(expectedQualifiers); + + assertThat(repositoryExtension.mappingContexts.isEmpty(), is(true)); + + repositoryExtension.processBean(mockProcessBean); + + assertThat(repositoryExtension.mappingContexts.containsKey(expectedQualifiers), is(true)); + assertThat(repositoryExtension.mappingContexts.get(expectedQualifiers), is(equalTo(mockBean))); + + verify(mockProcessBean, times(1)).getBean(); + verify(mockBean, times(2)).getTypes(); + verify(mockBean, times(2)).getQualifiers(); + } + + @Test + public void processBeanIgnoresNonRegionNonGemfireMappingContextBeansProperly() { + ProcessBean mockProcessBean = mock(ProcessBean.class); + Bean mockBean = mock(Bean.class); + + when(mockProcessBean.getBean()).thenReturn(mockBean); + when(mockBean.getTypes()).thenReturn(Collections.singleton((Type) Object.class)); + + assertThat(repositoryExtension.mappingContexts.isEmpty(), is(true)); + assertThat(repositoryExtension.regionBeans.isEmpty(), is(true)); + + repositoryExtension.processBean(mockProcessBean); + + assertThat(repositoryExtension.mappingContexts.isEmpty(), is(true)); + assertThat(repositoryExtension.regionBeans.isEmpty(), is(true)); + + verify(mockProcessBean, times(1)).getBean(); + verify(mockBean, times(1)).getTypes(); + } + + @Test + public void afterBeanDiscoveryRegistersRepositoryBean() { + AfterBeanDiscovery mockAfterBeanDiscovery = mock(AfterBeanDiscovery.class); + + final Set expectedQualifiers = asSet(mockAnnotation(SpringDataRepo.class), + mockAnnotation(GemfireRepo.class)); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + GemfireRepositoryBean repositoryBean = invocation.getArgumentAt(0, GemfireRepositoryBean.class); + + assertThat(repositoryBean, is(notNullValue())); + assertThat((Class) repositoryBean.getBeanClass(), is(equalTo(TestRepository.class))); + assertThat(repositoryBean.getQualifiers(), is(equalTo(expectedQualifiers))); + + return null; + } + }).when(mockAfterBeanDiscovery).addBean(isA(GemfireRepositoryBean.class)); + + GemfireRepositoryExtension repositoryExtension = new GemfireRepositoryExtension() { + @Override protected Iterable, Set>> getRepositoryTypes() { + return Collections., Set>singletonMap(TestRepository.class, expectedQualifiers).entrySet(); + } + }; + + repositoryExtension.afterBeanDiscovery(mockAfterBeanDiscovery, mock(BeanManager.class)); + + verify(mockAfterBeanDiscovery, times(1)).addBean(isA(GemfireRepositoryBean.class)); + } + + @Qualifier + @interface GemfireRepo { + } + + @Qualifier + @interface SpringDataRepo { + } + + @GemfireRepo + @SpringDataRepo + interface TestRepository extends GemfireRepository { + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/RepositoryClient.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/RepositoryClient.java new file mode 100644 index 00000000..959e00e5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/RepositoryClient.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import java.util.concurrent.atomic.AtomicLong; +import javax.inject.Inject; + +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.util.Assert; + +/** + * The RepositoryClient class is a user/consumer of the {@link SamplePersonRepository} bean in a CDI context. + * + * @author John Blum + * @see javax.inject.Inject + * @see org.springframework.data.gemfire.repository.cdi.SamplePersonRepository + * @since 1.8.0 + */ +public class RepositoryClient { + + private static final AtomicLong ID_SEQUENCE = new AtomicLong(0l); + + @Inject + private SamplePersonRepository personRepository; + + protected SamplePersonRepository getPersonRepository() { + Assert.state(personRepository != null, "personRepository was not properly initialized"); + return personRepository; + } + + public Person newPerson(String firstName, String lastName) { + return new Person(ID_SEQUENCE.incrementAndGet(), firstName, lastName); + } + + public Person find(Long id) { + return getPersonRepository().findOne(id); + } + + public Person save(Person person) { + return getPersonRepository().save(person); + } + + public boolean delete(Person person) { + getPersonRepository().delete(person); + return (find(person.getId()) == null); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java new file mode 100644 index 00000000..bc12dae1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepository.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.gemfire.repository.GemfireRepository; +import org.springframework.data.gemfire.repository.sample.Person; + +/** + * The SamplePersonRepository class is a {@link GemfireRepository} implementation for performing data access (CRUD) + * operations on instances of {@link Person}. + * + * @author John Blum + * @see org.springframework.data.gemfire.repository.GemfireRepository + * @see org.springframework.data.gemfire.repository.cdi.CustomPersonRepository + * @see org.springframework.data.gemfire.repository.cdi.SamplePersonRepositoryImpl + * @see org.springframework.data.gemfire.repository.sample.Person + * @since 1.8.0 + */ +@Region("People") +public interface SamplePersonRepository extends GemfireRepository, CustomPersonRepository { + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepositoryImpl.java b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepositoryImpl.java new file mode 100644 index 00000000..a0e1e4bc --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/cdi/SamplePersonRepositoryImpl.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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.data.gemfire.repository.cdi; + +/** + * The SamplePersonRepositoryImpl class is an implementation of the {@link CustomPersonRepository} extension interface + * supporting additional data access (CRUD) operations on people. + * + * @author John Blum + * @see org.springframework.data.gemfire.repository.cdi.CustomPersonRepository + * @since 1.8.0 + */ +public class SamplePersonRepositoryImpl implements CustomPersonRepository { + + public int returnOne() { + return 1; + } + +} diff --git a/src/test/resources/META-INF/beans.xml b/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000..002e5567 --- /dev/null +++ b/src/test/resources/META-INF/beans.xml @@ -0,0 +1,5 @@ + + + diff --git a/template.mf b/template.mf index 4e765496..5e79fb86 100644 --- a/template.mf +++ b/template.mf @@ -2,14 +2,14 @@ Bundle-ManifestVersion: 2 Bundle-Name: Spring Data GemFire Bundle-SymbolicName: org.springframework.data.gemfire Bundle-Vendor: Pivotal Software, Inc. -Import-Package: - sun.reflect;version="0";resolution:=optional -Import-Template: +Import-Package: sun.reflect;version="0";resolution:=optional +Import-Template: javax.enterprise.*;version="[1.0,2.0)";resolution:=optional, + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.aspectj.*;version="[1.8.2, 2.0.0)";resolution:=optional, com.fasterxml.jackson.*;version="[2.4.1,3.0.0)";resolution:=optional, com.gemstone.*;version="[8.0.0,9.0.0)", - org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, - org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", - org.aspectj.*;version="[1.8.2, 2.0.0)";resolution:=optional, + org.slf4j.*;version="[1.7.0,2.0)", org.springframework.*;version="[4.0.6, 5.0.0)", org.springframework.data.*;version="[1.9.0,2.0.0)", org.w3c.dom.*;version="0"