Implements JIRA ticket SGF-261 allowing an application domain object, or rather entity to be persisted to multiple Regions in the GemFire Cache.
This commit is contained in:
@@ -36,6 +36,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
@@ -58,11 +59,9 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
* @param regions must not be {@literal null}.
|
||||
* @param context
|
||||
*/
|
||||
public GemfireRepositoryFactory(Iterable<Region<?, ?>> regions,
|
||||
MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> context) {
|
||||
|
||||
public GemfireRepositoryFactory(Iterable<Region<?, ?>> regions, MappingContext<? extends GemfirePersistentEntity<?>,
|
||||
GemfirePersistentProperty> context) {
|
||||
Assert.notNull(regions);
|
||||
|
||||
this.context = context == null ? new GemfireMappingContext() : context;
|
||||
this.regions = new Regions(regions, this.context);
|
||||
}
|
||||
@@ -70,14 +69,12 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getEntityInformation(java.lang.Class)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getEntityInformation(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, ID extends Serializable> GemfireEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
|
||||
GemfirePersistentEntity<T> entity = (GemfirePersistentEntity<T>) context.getPersistentEntity(domainClass);
|
||||
return new DefaultGemfireEntityInformation<T, ID>(entity);
|
||||
}
|
||||
@@ -85,64 +82,54 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getTargetRepository(org.springframework.data.repository.core.
|
||||
* RepositoryMetadata)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected Object getTargetRepository(RepositoryMetadata metadata) {
|
||||
|
||||
GemfireEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
GemfireTemplate gemfireTemplate = getTemplate(metadata);
|
||||
|
||||
return new SimpleGemfireRepository(gemfireTemplate, entityInformation);
|
||||
}
|
||||
|
||||
private GemfireTemplate getTemplate(RepositoryMetadata metadata) {
|
||||
GemfirePersistentEntity<?> entity = context.getPersistentEntity(metadata.getDomainType());
|
||||
|
||||
Class<?> repositoryClass = metadata.getRepositoryInterface();
|
||||
Class<?> domainClass = metadata.getDomainType();
|
||||
|
||||
GemfirePersistentEntity<?> entity;
|
||||
if (repositoryClass.isAnnotationPresent(org.springframework.data.gemfire.mapping.Region.class)) {
|
||||
entity = context.getPersistentEntity(repositoryClass);
|
||||
}
|
||||
else {
|
||||
entity = context.getPersistentEntity(domainClass);
|
||||
}
|
||||
String entityRegionName = entity.getRegionName();
|
||||
String repositoryRegionName = getRepositoryRegionName(metadata.getRepositoryInterface());
|
||||
String regionName = (StringUtils.hasText(repositoryRegionName) ? repositoryRegionName : entityRegionName);
|
||||
|
||||
Region<?, ?> region = regions.getRegion(entity.getRegionName());
|
||||
Region<?, ?> region = regions.getRegion(regionName);
|
||||
|
||||
if (region == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No region '%s' found for domain class %s! Make sure you have "
|
||||
+ "configured a Gemfire region of that name in your application context!",
|
||||
entity.getRegionName(), domainClass));
|
||||
throw new IllegalStateException(String.format("No region '%s' found for domain class %s!"
|
||||
+ " Make sure you have configured a Gemfire region of that name in your application context!",
|
||||
regionName, metadata.getDomainType()));
|
||||
}
|
||||
|
||||
Class<?> regionKeyType = region.getAttributes().getKeyConstraint();
|
||||
Class<?> entityIdType = metadata.getIdType();
|
||||
|
||||
if (regionKeyType != null && entity.getIdProperty() != null) {
|
||||
Assert.isTrue(
|
||||
regionKeyType.isAssignableFrom(entityIdType),
|
||||
String.format(
|
||||
"The region referenced only supports keys of type %s but the entity to be stored has an id of type %s!",
|
||||
regionKeyType, entityIdType));
|
||||
Assert.isTrue(regionKeyType.isAssignableFrom(entityIdType), String.format(
|
||||
"The region referenced only supports keys of type %s but the entity to be stored has an id of type %s!",
|
||||
regionKeyType, entityIdType));
|
||||
}
|
||||
|
||||
return new GemfireTemplate(region);
|
||||
}
|
||||
|
||||
private String getRepositoryRegionName(final Class<?> repositoryClass) {
|
||||
return (repositoryClass.isAnnotationPresent(org.springframework.data.gemfire.mapping.Region.class) ?
|
||||
repositoryClass.getAnnotation(org.springframework.data.gemfire.mapping.Region.class).value() : null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getRepositoryBaseClass(org.springframework.data.repository.core.
|
||||
* RepositoryMetadata)
|
||||
*
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
|
||||
@@ -152,10 +139,8 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getQueryLookupStrategy(org.springframework.data.repository.query.
|
||||
* QueryLookupStrategy.Key)
|
||||
* @see springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key)
|
||||
*/
|
||||
@Override
|
||||
protected QueryLookupStrategy getQueryLookupStrategy(Key key) {
|
||||
@@ -180,4 +165,5 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2010-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.data.gemfire.repository.sample;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
@@ -56,7 +72,7 @@ public class Animal {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{ type = %1$s, id = %2$d, name = %3$s }", getClass().getSimpleName(), getId(), getName());
|
||||
return String.format("{ @type = %1$s, id = %2$d, name = %3$s }", getClass().getSimpleName(), getId(), getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
/*
|
||||
* Copyright 2010-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.data.gemfire.repository.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -11,11 +26,18 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* The AnimalRepositoryTest class is a test suite of test cases testing the functionality behind PR #55 involving
|
||||
* persisting application domain object/entities to multiple Regions in GemFire's Cache.
|
||||
* <p/>
|
||||
* @author Stuart Williams
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ContextConfiguration("AnimalRepositoryTest-context.xml")
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SuppressWarnings("unused")
|
||||
public class AnimalRepositoryTest {
|
||||
@@ -26,9 +48,6 @@ public class AnimalRepositoryTest {
|
||||
@Autowired
|
||||
private DogRepository dogRepo;
|
||||
|
||||
@Autowired
|
||||
private RabbitRepository rabbitRepo;
|
||||
|
||||
protected static Animal createAnimal(final long id, final String name) {
|
||||
Animal animal = new Animal();
|
||||
animal.setId(id);
|
||||
@@ -76,20 +95,4 @@ public class AnimalRepositoryTest {
|
||||
assertEquals(foundCerberusTheDog, dogRepo.findOne(3L));
|
||||
}
|
||||
|
||||
@Test(expected = ClassCastException.class)
|
||||
public void testStoreAnimalHavingLongIdInRabbitsRegionWithStringKey() {
|
||||
try {
|
||||
rabbitRepo.save(createAnimal(123L, "Harry"));
|
||||
}
|
||||
// NOTE the ClassCastException thrown from GemFire is expected; this is not correct and the identifying type
|
||||
// mismatch should have been caught by GemfireRepositoryFactory.getTemplate(..) method on line 129
|
||||
// (appropriately throwing an IllegalArgumentException) after satisfying the condition on line 128,
|
||||
// if only the @Region annotation were set on the domain class/entity!
|
||||
catch (ClassCastException unexpected) {
|
||||
//unexpected.printStackTrace(System.err);
|
||||
assertTrue(unexpected.getMessage().contains("key ( java.lang.Long ) does not satisfy keyConstraint ( java.lang.String )"));
|
||||
throw unexpected;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-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.data.gemfire.repository.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* The IncompatibleRegionKeyEntityIdAnimalRepositoryTest class is a test suite of test cases testing the functionality
|
||||
* behind PR #55 involving persisting application domain object/entities to multiple Regions in GemFire's Cache.
|
||||
* <p/>
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @since 1.4.0
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
public class IncompatibleRegionKeyEntityIdAnimalRepositoryTest {
|
||||
|
||||
protected static final String APPLICATION_CONTEXT_CONFIG_LOCATION = String.format("%1$s%2$s%1$s%3$s",
|
||||
File.separator, AnimalRepositoryTest.class.getPackage().getName().replace('.', File.separatorChar),
|
||||
"IncompatibleRegionKeyEntityIdAnimalRepositoryTest-context.xml");
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testStoreAnimalHavingLongIdInRabbitsRegionWithStringKey() {
|
||||
try {
|
||||
ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext(
|
||||
APPLICATION_CONTEXT_CONFIG_LOCATION);
|
||||
applicationContext.getBean(RabbitRepository.class);
|
||||
}
|
||||
// NOTE the ClassCastException thrown from GemFire is unexpected; this is not correct and the identifying type
|
||||
// mismatch should be caught and handled by GemfireRepositoryFactory.getTemplate(..) method on line 129
|
||||
// (appropriately throwing an IllegalArgumentException) after satisfying the condition on line 128,
|
||||
// which always occurs with the @Region annotation set on the domain class/entity!
|
||||
catch (ClassCastException unexpected) {
|
||||
//unexpected.printStackTrace(System.err);
|
||||
//assertTrue(unexpected.getMessage().contains("key ( java.lang.Long ) does not satisfy keyConstraint ( java.lang.String )"));
|
||||
fail(unexpected.getMessage());
|
||||
}
|
||||
catch (BeanCreationException expected) {
|
||||
//expected.printStackTrace(System.err);
|
||||
assertTrue(expected.getCause() instanceof IllegalArgumentException);
|
||||
assertEquals(String.format("The region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!",
|
||||
String.class, Long.class), expected.getCause().getMessage());
|
||||
throw (IllegalArgumentException) expected.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class Plant {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{ type = %1$s, id = %2$s, name = %3$s }", getClass().getSimpleName(), getId(), getName());
|
||||
return String.format("{ @type = %1$s, id = %2$s, name = %3$s }", getClass().getSimpleName(), getId(), getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.springframework.data.gemfire.repository.sample;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -29,19 +31,22 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
* persisting application domain object/entities to multiple Regions in GemFire's Cache.
|
||||
* <p/>
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @since 1.4.0
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
public class PlantRepositoryTest {
|
||||
|
||||
protected static final String APPLICATION_CONTEXT_CONFIG_LOCATION =
|
||||
"/org/springframework/data/gemfire/repository/sample/PlantRepositoryTest-context.xml";
|
||||
protected static final String APPLICATION_CONTEXT_CONFIG_LOCATION = String.format("%1$s%2$s%1$s%3$s",
|
||||
File.separator, PlantRepositoryTest.class.getPackage().getName().replace('.', File.separatorChar),
|
||||
"PlantRepositoryTest-context.xml");
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testStorePlantHavingStringIdInPlantsRegionWithLongKey() {
|
||||
try {
|
||||
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION_CONTEXT_CONFIG_LOCATION);
|
||||
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
APPLICATION_CONTEXT_CONFIG_LOCATION);
|
||||
context.getBean(PlantRepository.class);
|
||||
}
|
||||
// NOTE technically, the IllegalArgumentException for incompatible Region 'Key' and Entity ID is thrown
|
||||
|
||||
@@ -31,12 +31,6 @@
|
||||
key-constraint="java.lang.Long"
|
||||
value-constraint="org.springframework.data.gemfire.repository.sample.Animal"/>
|
||||
|
||||
<gfe:replicated-region
|
||||
id="Rabbits"
|
||||
persistent="false"
|
||||
key-constraint="java.lang.String"
|
||||
value-constraint="org.springframework.data.gemfire.repository.sample.Animal"/>
|
||||
|
||||
<gfe-data:repositories base-package="org.springframework.data.gemfire.repository.sample"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:gfe-data="http://www.springframework.org/schema/data/gemfire"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/gemfire http://www.springframework.org/schema/data/gemfire/spring-data-gemfire.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="peerCacheConfigurationSettings">
|
||||
<prop key="name">springGemFireIncompatibleRegionKeyEntityIdAnimalRepositoryTest</prop>
|
||||
<prop key="log-level">config</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:cache properties-ref="peerCacheConfigurationSettings"/>
|
||||
|
||||
<gfe:replicated-region
|
||||
id="Rabbits"
|
||||
persistent="false"
|
||||
key-constraint="java.lang.String"
|
||||
value-constraint="org.springframework.data.gemfire.repository.sample.Animal"/>
|
||||
|
||||
<gfe-data:repositories base-package="org.springframework.data.gemfire.repository.sample"/>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user