diff --git a/build.gradle b/build.gradle index 27e33509..03aafdc9 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ allprojects { mavenRepo name: "spring-snapshot", urls: "http://maven.springframework.org/snapshot" mavenRepo name: "sonatype-snapshot", urls: "http://oss.sonatype.org/content/repositories/snapshots" mavenRepo name: "ext-snapshots", urls: "http://springframework.svn.sourceforge.net/svnroot/springframework/repos/repo-ext/" - mavenRepo name: "gemstone-com-release", urls: "http://dist.gemstone.com/maven/release" + mavenRepo name: "gemstone-com-release", urls: "http://repo.springsource.org/gemstone-release" } } @@ -87,10 +87,15 @@ dependencies { compile("com.gemstone.gemfire:gemfire:$gemfireVersion") // Testing - testCompile "junit:junit:$junitVersion" + testCompile "junit:junit-dep:$junitVersion" testCompile "org.mockito:mockito-core:$mockitoVersion" + testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" + testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" testCompile "org.springframework:spring-test:$springVersion" testCompile("javax.annotation:jsr250-api:1.0") { optional = true } + + // Spring Data + compile "org.springframework.data:spring-data-commons-core:${springDataCommonsVersion}" } javaprojects = rootProject diff --git a/docs/src/reference/docbook/index.xml b/docs/src/reference/docbook/index.xml index b6bc2654..48119e0b 100644 --- a/docs/src/reference/docbook/index.xml +++ b/docs/src/reference/docbook/index.xml @@ -13,6 +13,11 @@ Leau SpringSource, a division of VMware + + Oliver + Gierke + SpringSource, a division of VMware + @@ -41,6 +46,11 @@ + + + + + diff --git a/docs/src/reference/docbook/reference/mapping.xml b/docs/src/reference/docbook/reference/mapping.xml new file mode 100644 index 00000000..03409452 --- /dev/null +++ b/docs/src/reference/docbook/reference/mapping.xml @@ -0,0 +1,84 @@ + + + POJO mapping + +
+ Entity mapping + + Spring Data Gemfire provides support to map entities to be stored in + a Gemfire grid. The mapping metadata is define by using annotations at the + domain classes just like this: + + + Mapping a domain class to Gemfire + + @Region("myRegion") +public class Person { + + @Id Long id; + String firstname; + String lastname; + + @PersistenceConstructor + public Person(String firstname, String lastname) { + // … + } + + … +} + + + The first thing you see here is the + @Region annotation that can be used to + customize the region instances of the Person class + are stored in. The @Id annotation can be + used to annotate the property that shall be used as cache key. The + @PersistenceConstructor annotation actually + helps disambiguing multiple potentially available constructors taking + parameters and explicitly marking the one annotated as the one to be used + to create entities. With none or only a single constructor you can omit + the annotation. +
+ +
+ Mapping PDX serializer + + Spring Data Gemfire provides a custom + PDXSerializer implementation that uses the + mapping information to customize entity serialization. Beyond that it + allows customizing the entity instantiation by using the Spring Data + EntityInstantiator abstraction. By default + the serializer uses a ReflectionEntityInstantiator + that will use the persistence constructor of the mapped entity (either the + single declared one or explicitly annoted with + @PersistenceConstructor). To provide values + for constructor parameters it will read fields with name of the + constructor parameters from the PDXReader + supplied. + + + Using @Value on entity constructor parameters + + public class Person { + + public Person(@Value("#root.foo") String firstname, @Value("bean") String lastname) { + // … + } + + … +} + + + The entity annotated as such will get the field foo + read from the PDXReader and handed as + constructor parameter value for firstname. The value for + lastname will be the Spring bean with name + bean. +
+
diff --git a/docs/src/reference/docbook/reference/repositories.xml b/docs/src/reference/docbook/reference/repositories.xml new file mode 100644 index 00000000..1e4c0cbe --- /dev/null +++ b/docs/src/reference/docbook/reference/repositories.xml @@ -0,0 +1,218 @@ + + + Gemfire Repositories + +
+ Introduction + + Spring Data Gemfire provides support to use the Spring Data + repository abstraction to easily persist entities into Gemfire and execute + queries. A general introduction into the repository programmin model has + been provided in . +
+ +
+ Spring configuration + + To bootstrap Spring Data repositories you use the + <repositories /> element from the Gemfire + namespace: + + + Bootstrap Gemfire repositories + + <beans xmlns="http://www.springframework.org/schema/beans" + xmlns:gf="http://www.springframework.org/schema/gemfire" + 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> + + <gf:repositories base-package="com.acme.repository" /> + +</beans> + + + This configuration snippet will look for interfaces below the + configured base package and create repository instances for those + interfaces backed by a SimpleGemfireRepository. + Note that you have to have your domain classes correctly mapped to + configured regions as the bottstrap process will fail otherwise. +
+ +
+ Executing OQL queries + + The Gemfire repositories allow the definition of query methods to + easily execute OQL queries against the Region the managed entity is mapped + to. + + + Sample repository + + @Region("myRegion") +public class Person { … } + + public interface PersonRepository extends CrudRepository<Person, Long> { + + Person findByEmailAddress(String emailAddress); + + Collection<Person> findByFirstname(String firstname); + + @Query("SELECT * FROM /Person p WHERE p.firstname = $1") + Collection<Person> findByFirstnameAnnotated(String firstname); + + @Query("SELECT * FROM /Person p WHERE p.firstname IN SET $1") + Collection<Person> findByFirstnamesAnnotated(Collection<String> firstnames); +} + + + The first method listed here will cause the following query to be + derived: SELECT x FROM /myRegion x WHERE x.emailAddress = $1. + The second method works the same way except it's returning all entities + found whereas the first one expects a single result value. In case the + supported keywords are not sufficient to declare your query or the method + name gets to verbose you can annotate the query methods with + @Query as seen for methods 3 and 4. + + + Supported keywords for query methods + + + + + + + + + + + Keyword + + Sample + + Logical result + + + + + + GreaterThan + + findByAgeGreaterThan(int + age) + + x.age > $1 + + + + GreaterThanEqual + + findByAgeGreaterThanEqual(int + age) + + x.age >= $1 + + + + LessThan + + findByAgeLessThan(int + age) + + x.age < $1 + + + + LessThanEqual + + findByAgeLessThanEqual(int + age) + + x.age <= $1 + + + + IsNotNull, + NotNull + + findByFirstnameNotNull() + + x.firstname =! NULL + + + + IsNull, + Null + + findByFirstnameNull() + + x.firstname = NULL + + + + In + + findByFirstnameIn(Collection<String> + x) + + x.firstname IN SET $1 + + + + NotIn + + findByFirstnameNotIn(Collection<String> + x) + + x.firstname NOT IN SET $1 + + + + (No keyword) + + findByFirstname(String + name) + + x.firstname = $1 + + + + Not + + findByFirstnameNot(String + name) + + x.firstname != $1 + + + + IsTrue, + True + + findByActiveIsTrue() + + x.active = true + + + + IsFalse, + False + + findByActiveIsFalse() + + x.active = false + + + +
+
+
diff --git a/gradle.properties b/gradle.properties index cc97c3f2..db667a51 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,11 +6,13 @@ slf4jVersion = 1.6.4 # Common libraries springVersion = 3.1.0.RELEASE +springDataCommonsVersion = 1.3.0.M1 gemfireVersion = 6.6.1 # Testing junitVersion = 4.8.1 mockitoVersion = 1.8.5 +hamcrestVersion = 1.2.1 # Manifest properties diff --git a/src/main/java/org/springframework/data/gemfire/config/GemfireNamespaceHandler.java b/src/main/java/org/springframework/data/gemfire/config/GemfireNamespaceHandler.java index 57eeafe6..68f52bfa 100644 --- a/src/main/java/org/springframework/data/gemfire/config/GemfireNamespaceHandler.java +++ b/src/main/java/org/springframework/data/gemfire/config/GemfireNamespaceHandler.java @@ -17,6 +17,7 @@ package org.springframework.data.gemfire.config; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; +import org.springframework.data.gemfire.repository.config.GemfireRepositoryParser; /** * Namespace handler for GemFire definitions. @@ -40,5 +41,7 @@ class GemfireNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("transaction-manager", new TransactionManagerParser()); registerBeanDefinitionParser("cq-listener-container", new GemfireListenerContainerParser()); + + registerBeanDefinitionParser("repositories", new GemfireRepositoryParser()); } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java new file mode 100644 index 00000000..bc5c47fb --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java @@ -0,0 +1,50 @@ +/* + * 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.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +/** + * + * @author Oliver Gierke + */ +public class GemfireMappingContext extends + AbstractMappingContext, GemfirePersistentProperty> { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ + @Override + protected GemfirePersistentEntity createPersistentEntity(TypeInformation typeInformation) { + return new GemfirePersistentEntity(typeInformation); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) + */ + @Override + protected GemfirePersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, + GemfirePersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + return new GemfirePersistentProperty(field, descriptor, owner, simpleTypeHolder); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java new file mode 100644 index 00000000..9cb5878e --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntity.java @@ -0,0 +1,57 @@ +/* + * 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.mapping; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.StringUtils; + +/** + * {@link PersistentEntity} implementation adding custom Gemfire related metadata, such as the region the entity is + * mapped to etc. + * + * @author Oliver Gierke + */ +public class GemfirePersistentEntity extends BasicPersistentEntity { + + private final String regionName; + + /** + * Creates a new {@link GemfirePersistentEntity} for the given {@link TypeInformation}. + * + * @param information must not be {@literal null}. + */ + public GemfirePersistentEntity(TypeInformation information) { + + super(information); + + Class rawType = information.getType(); + Region region = rawType.getAnnotation(Region.class); + String fallbackName = rawType.getSimpleName(); + + this.regionName = region == null || !StringUtils.hasText(region.value()) ? fallbackName : region.value(); + } + + /** + * Returns the name of the region the entity shall be stored in. + * + * @return + */ + public String getRegionName() { + return this.regionName; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentProperty.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentProperty.java new file mode 100644 index 00000000..ea04d880 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePersistentProperty.java @@ -0,0 +1,52 @@ +/* + * 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.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * {@link PersistentProperty} implementation to for Gemfire related metadata. + * + * @author Oliver Gierke + */ +public class GemfirePersistentProperty extends AnnotationBasedPersistentProperty { + + /** + * @param field + * @param propertyDescriptor + * @param owner + * @param simpleTypeHolder + */ + public GemfirePersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + /* (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ + @Override + protected Association createAssociation() { + return null; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfirePropertyValueProvider.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePropertyValueProvider.java new file mode 100644 index 00000000..e674a47d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfirePropertyValueProvider.java @@ -0,0 +1,51 @@ +/* + * 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.mapping; + +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.pdx.PdxReader; + +/** + * {@link PropertyValueProvider} to read property values from a {@link PdxReader}. + * + * @author Oliver Gierke + */ +class GemfirePropertyValueProvider implements PropertyValueProvider { + + private final PdxReader reader; + + /** + * Creates a new {@link GemfirePropertyValueProvider} with the given {@link PdxReader}. + * + * @param reader must not be {@literal null}. + */ + public GemfirePropertyValueProvider(PdxReader reader) { + Assert.notNull(reader); + this.reader = reader; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) + */ + @Override + @SuppressWarnings("unchecked") + public T getPropertyValue(GemfirePersistentProperty property) { + return (T) reader.readObject(property.getName()); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java new file mode 100644 index 00000000..439b92d2 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/MappingPdxSerializer.java @@ -0,0 +1,156 @@ +/* + * 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.mapping; + +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.SpELContext; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.pdx.PdxReader; +import com.gemstone.gemfire.pdx.PdxSerializer; +import com.gemstone.gemfire.pdx.PdxWriter; + +/** + * {@link PdxSerializer} implementation that uses a {@link GemfireMappingContext} to read and write entities. + * + * @author Oliver Gierke + */ +public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAware { + + private final GemfireMappingContext mappingContext; + private final ConversionService conversionService; + + private EntityInstantiators instantiators; + private SpELContext context; + + /** + * Creates a new {@link MappingPdxSerializer} using the given {@link GemfireMappingContext} and + * {@link ConversionService}. + * + * @param mappingContext must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionService conversionService) { + + Assert.notNull(mappingContext); + Assert.notNull(conversionService); + + this.mappingContext = mappingContext; + this.conversionService = conversionService; + this.instantiators = new EntityInstantiators(); + this.context = new SpELContext(PdxReaderPropertyAccessor.INSTANCE); + } + + /** + * Configures the {@link EntityInstantiator}s to be used to create the instances to be read. + * + * @param gemfireInstantiators must not be {@literal null}. + */ + public void setGemfireInstantiators(Map, EntityInstantiator> gemfireInstantiators) { + Assert.notNull(gemfireInstantiators); + this.instantiators = new EntityInstantiators(gemfireInstantiators); + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = new SpELContext(context, applicationContext); + } + + /* + * (non-Javadoc) + * @see com.gemstone.gemfire.pdx.PdxSerializer#fromData(java.lang.Class, com.gemstone.gemfire.pdx.PdxReader) + */ + public Object fromData(Class type, final PdxReader reader) { + + final GemfirePersistentEntity entity = mappingContext.getPersistentEntity(type); + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + GemfirePropertyValueProvider propertyValueProvider = new GemfirePropertyValueProvider(reader); + + PersistentEntityParameterValueProvider provider = new PersistentEntityParameterValueProvider( + entity, propertyValueProvider); + provider.setSpELEvaluator(new DefaultSpELExpressionEvaluator(reader, context)); + + Object instance = instantiator.createInstance(entity, provider); + + final BeanWrapper, Object> wrapper = BeanWrapper.create(instance, conversionService); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(GemfirePersistentProperty persistentProperty) { + + if (entity.isConstructorArgument(persistentProperty)) { + return; + } + + Object value = reader.readField(persistentProperty.getName()); + + try { + wrapper.setProperty(persistentProperty, value); + } catch (Exception e) { + throw new MappingException("Could not read value " + value.toString(), e); + } + } + }); + + return wrapper.getBean(); + } + + /* + * (non-Javadoc) + * @see com.gemstone.gemfire.pdx.PdxSerializer#toData(java.lang.Object, com.gemstone.gemfire.pdx.PdxWriter) + */ + public boolean toData(Object value, final PdxWriter writer) { + + GemfirePersistentEntity entity = mappingContext.getPersistentEntity(value.getClass()); + final BeanWrapper, Object> wrapper = BeanWrapper.create(value, conversionService); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(GemfirePersistentProperty persistentProperty) { + + try { + Object value = wrapper.getProperty(persistentProperty); + writer.writeObject(persistentProperty.getName(), value); + } catch (Exception e) { + throw new MappingException("Could not write value for property " + persistentProperty.toString(), e); + } + } + }); + + GemfirePersistentProperty idProperty = entity.getIdProperty(); + + if (idProperty != null) { + writer.markIdentityField(idProperty.getName()); + } + + return true; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessor.java b/src/main/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessor.java new file mode 100644 index 00000000..4e52e7b9 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessor.java @@ -0,0 +1,78 @@ +/* + * 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.mapping; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; + +import com.gemstone.gemfire.pdx.PdxReader; + +/** + * {@link PropertyAccessor} to read values from a {@link PdxReader}. + * + * @author Oliver Gierke + */ +enum PdxReaderPropertyAccessor implements PropertyAccessor { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#getSpecificTargetClasses() + */ + @Override + public Class[] getSpecificTargetClasses() { + return new Class[] { PdxReader.class }; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + @Override + public boolean canRead(EvaluationContext context, Object target, String name) { + return ((PdxReader) target).hasField(name); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + @Override + public TypedValue read(EvaluationContext context, Object target, String name) { + Object object = ((PdxReader) target).readObject(name); + return object == null ? TypedValue.NULL : new TypedValue(object); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canWrite(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + @Override + public boolean canWrite(EvaluationContext context, Object target, String name) { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#write(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.lang.Object) + */ + @Override + public void write(EvaluationContext context, Object target, String name, Object newValue) { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/mapping/Region.java b/src/main/java/org/springframework/data/gemfire/mapping/Region.java new file mode 100644 index 00000000..726c0478 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/Region.java @@ -0,0 +1,42 @@ +/* + * 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.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define the region an entity will be stored in. + * + * @author Oliver Gierke + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface Region { + + /** + * The name of the {@link com.gemstone.gemfire.cache.Region} the entity shall be stored in. + * + * @return the name of the region the entity shall be persisted in. + */ + String value() default ""; +} diff --git a/src/main/java/org/springframework/data/gemfire/mapping/Regions.java b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java new file mode 100644 index 00000000..70c3ebb6 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java @@ -0,0 +1,98 @@ +/* + * 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.mapping; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +import com.gemstone.bp.edu.emory.mathcs.backport.java.util.Collections; +import com.gemstone.gemfire.cache.Region; + +/** + * Simple value object to abstract access to regions by name and mapped type. + * + * @author Oliver Gierke + */ +public class Regions implements Iterable> { + + private final Map> regions; + private final MappingContext, ?> context; + + /** + * Creates a new {@link Regions} wrapper for the given {@link Region}s and {@link MappingContext}. + * + * @param regions must not be {@literal null}. + * @param context must not be {@literal null}. + */ + @SuppressWarnings("unchecked") + public Regions(Iterable> regions, MappingContext, ?> context) { + + Assert.notNull(regions); + Assert.notNull(context); + + Map> regionMap = new HashMap>(); + + for (Region region : regions) { + regionMap.put(region.getName(), region); + } + + this.regions = Collections.unmodifiableMap(regionMap); + this.context = context; + } + + /** + * Returns the {@link Region} the given type is mapped to. Will try to find a {@link Region} with the simple class + * name in case no mapping information is found. + * + * @param type must not be {@literal null}. + * @return + */ + @SuppressWarnings("unchecked") + public Region getRegion(Class type) { + + Assert.notNull(type); + + GemfirePersistentEntity entity = context.getPersistentEntity(type); + return (Region) (entity == null ? regions.get(type.getSimpleName()) : regions.get(entity.getRegionName())); + } + + /** + * Returns the region with the given name. + * + * @param name must not be {@literal null}. + * @return + */ + @SuppressWarnings("unchecked") + public Region getRegion(String name) { + + Assert.notNull(name); + + return (Region) regions.get(name); + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator> iterator() { + return regions.values().iterator(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/GemfireRepository.java b/src/main/java/org/springframework/data/gemfire/repository/GemfireRepository.java new file mode 100644 index 00000000..ec4b935c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/GemfireRepository.java @@ -0,0 +1,30 @@ +/* + * 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; + +import java.io.Serializable; + +import org.springframework.data.repository.CrudRepository; + +/** + * Gemfire-specific extension of the {@link CrudRepository} interface. + * + * @author Oliver Gierke + */ +public interface GemfireRepository extends CrudRepository { + + T save(Wrapper wrapper); +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/Query.java b/src/main/java/org/springframework/data/gemfire/repository/Query.java new file mode 100644 index 00000000..b6e6cfd2 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/Query.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; + +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; + +/** + * + * @author Oliver Gierke + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Query { + + String value() default ""; +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/Wrapper.java b/src/main/java/org/springframework/data/gemfire/repository/Wrapper.java new file mode 100644 index 00000000..97a664a6 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/Wrapper.java @@ -0,0 +1,95 @@ +/* + * 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; + +import java.io.Serializable; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Simple value object to hold an entity alongside an external key the entity shall be stored under. + * + * @author Oliver Gierke + */ +public final class Wrapper { + + private final KEY key; + private final T entity; + + /** + * The entity to handle as well as the key. + * + * @param entity + * @param key must not be {@literal null}. + */ + public Wrapper(T entity, KEY key) { + + Assert.notNull(key); + + this.entity = entity; + this.key = key; + } + + /** + * @return the key + */ + public KEY getKey() { + return key; + } + + /** + * @return the entity + */ + public T getEntity() { + return entity; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object value) { + + if (this == value) { + return true; + } + + if (!(value instanceof Wrapper)) { + return false; + } + + Wrapper that = (Wrapper) value; + + return this.key.equals(that.key) && ObjectUtils.nullSafeEquals(this.entity, that.entity); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int result = 17; + + result += 31 * key.hashCode(); + result += 31 * ObjectUtils.nullSafeHashCode(entity); + + return result; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryParser.java b/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryParser.java new file mode 100644 index 00000000..7b6c40dd --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/config/GemfireRepositoryParser.java @@ -0,0 +1,40 @@ +/* + * 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.config; + +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.data.gemfire.repository.config.SimpleGemfireRepositoryConfiguration.GemfireRepositoryConfiguration; +import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; +import org.springframework.data.repository.config.AbstractRepositoryConfigDefinitionParser; +import org.w3c.dom.Element; + +/** + * {@link BeanDefinitionParser} to create {@link GemfireRepositoryFactoryBean}. + * + * @author Oliver Gierke + */ +public class GemfireRepositoryParser extends + AbstractRepositoryConfigDefinitionParser { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.AbstractRepositoryConfigDefinitionParser#getGlobalRepositoryConfigInformation(org.w3c.dom.Element) + */ + @Override + protected SimpleGemfireRepositoryConfiguration getGlobalRepositoryConfigInformation(Element element) { + return new SimpleGemfireRepositoryConfiguration(element); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/config/SimpleGemfireRepositoryConfiguration.java b/src/main/java/org/springframework/data/gemfire/repository/config/SimpleGemfireRepositoryConfiguration.java new file mode 100644 index 00000000..afa04ff7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/config/SimpleGemfireRepositoryConfiguration.java @@ -0,0 +1,135 @@ +/* + * 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.config; + +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.repository.config.SimpleGemfireRepositoryConfiguration.GemfireRepositoryConfiguration; +import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; +import org.springframework.data.repository.config.AutomaticRepositoryConfigInformation; +import org.springframework.data.repository.config.ManualRepositoryConfigInformation; +import org.springframework.data.repository.config.RepositoryConfig; +import org.springframework.data.repository.config.SingleRepositoryConfigInformation; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Repository configuration implementation. + * + * @author Oliver Gierke + */ +class SimpleGemfireRepositoryConfiguration extends + RepositoryConfig { + + private static final String GEMFIRE_TEMPLATE_REF = "gemfire-template-ref"; + + /** + * Creates a new {@link SimpleGemfireRepositoryConfiguration} for the given {@link Element}. + * + * @param repositoriesElement must not be {@literal null}. + */ + protected SimpleGemfireRepositoryConfiguration(Element repositoriesElement) { + super(repositoriesElement, GemfireRepositoryFactoryBean.class.getName()); + } + + /** + * Returns the bean name of the {@link GemfireTemplate} to be used. + * + * @return + */ + String getGemfireTemplateRef() { + + String attribute = getSource().getAttribute(GEMFIRE_TEMPLATE_REF); + return StringUtils.hasText(attribute) ? attribute : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.GlobalRepositoryConfigInformation#getAutoconfigRepositoryInformation(java.lang.String) + */ + @Override + public GemfireRepositoryConfiguration getAutoconfigRepositoryInformation(String interfaceName) { + return new AutomaticGemfireRepositoryConfiguration(interfaceName, this); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.CommonRepositoryConfigInformation#getNamedQueriesLocation() + */ + @Override + public String getNamedQueriesLocation() { + return "classpath*:META-INF/gemfire-named-queries.properties"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfig#createSingleRepositoryConfigInformationFor(org.w3c.dom.Element) + */ + @Override + protected GemfireRepositoryConfiguration createSingleRepositoryConfigInformationFor(Element element) { + return new ManualGemfireRepositoryConfiguration(element, this); + } + + public interface GemfireRepositoryConfiguration extends + SingleRepositoryConfigInformation { + + String getGemfireTemplateRef(); + } + + static class ManualGemfireRepositoryConfiguration extends + ManualRepositoryConfigInformation implements GemfireRepositoryConfiguration { + + /** + * @param element + * @param parent + */ + public ManualGemfireRepositoryConfiguration(Element element, SimpleGemfireRepositoryConfiguration parent) { + super(element, parent); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.config.GemfireRepositoryParser.SimpleGemfireRepositoryConfiguration.GemfireRepositoryConfiguration#getGemfireTemplateRef() + */ + @Override + public String getGemfireTemplateRef() { + return getAttribute(GEMFIRE_TEMPLATE_REF); + } + + } + + static class AutomaticGemfireRepositoryConfiguration extends + AutomaticRepositoryConfigInformation implements + GemfireRepositoryConfiguration { + + /** + * @param interfaceName + * @param parent + */ + public AutomaticGemfireRepositoryConfiguration(String interfaceName, SimpleGemfireRepositoryConfiguration parent) { + super(interfaceName, parent); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.config.GemfireRepositoryParser.SimpleGemfireRepositoryConfiguration.GemfireRepositoryConfiguration#getGemfireTemplateRef() + */ + @Override + public String getGemfireTemplateRef() { + + return getParent().getGemfireTemplateRef(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/repository/package-info.java b/src/main/java/org/springframework/data/gemfire/repository/package-info.java new file mode 100644 index 00000000..d5292d78 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/package-info.java @@ -0,0 +1,5 @@ +/** + * Implementations of Spring Data COmmons Core repository abstraction. + */ +package org.springframework.data.gemfire.repository; + diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/DefaultGemfireEntityInformation.java b/src/main/java/org/springframework/data/gemfire/repository/query/DefaultGemfireEntityInformation.java new file mode 100644 index 00000000..944f0d9c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/DefaultGemfireEntityInformation.java @@ -0,0 +1,52 @@ +/* + * 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.query; + +import java.io.Serializable; + +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.repository.core.support.DelegatingEntityInformation; +import org.springframework.data.repository.core.support.ReflectionEntityInformation; + +/** + * Implementation of {@link GemfireEntityInformation} using reflection to lookup region names. + * + * @author Oliver Gierke + */ +public class DefaultGemfireEntityInformation extends DelegatingEntityInformation + implements GemfireEntityInformation { + + private final GemfirePersistentEntity entity; + + /** + * Creates a new {@link DefaultGemfireEntityInformation}. + * + * @param domainClass must not be {@literal null}. + */ + public DefaultGemfireEntityInformation(GemfirePersistentEntity entity) { + super(new ReflectionEntityInformation(entity.getType())); + this.entity = entity; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.repository.query.GemfireEntityInformation#getRegionName() + */ + @Override + public String getRegionName() { + return entity.getRegionName(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireEntityInformation.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireEntityInformation.java new file mode 100644 index 00000000..1b44fde7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireEntityInformation.java @@ -0,0 +1,37 @@ +/* + * 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.query; + +import java.io.Serializable; + +import org.springframework.data.repository.core.EntityInformation; + +import com.gemstone.gemfire.cache.Region; + +/** + * {@link EntityInformation} to capture Gemfire specific information. + * + * @author Oliver Gierke + */ +public interface GemfireEntityInformation extends EntityInformation { + + /** + * Returns the name of the {@link Region} the entity is held in. + * + * @return + */ + String getRegionName(); +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java new file mode 100644 index 00000000..0d28e039 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java @@ -0,0 +1,143 @@ +/* + * 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.query; + +import java.util.Iterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.data.domain.Sort; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * Query creator to create {@link QueryString} instances. + * + * @author Oliver Gierke + */ +class GemfireQueryCreator extends AbstractQueryCreator { + + private static final Log LOG = LogFactory.getLog(GemfireQueryCreator.class); + + private final QueryBuilder query; + private Iterator indexes; + + /** + * Creates a new {@link GemfireQueryCreator} using the given {@link PartTree} and domain class. + * + * @param tree must not be {@literal null}. + * @param entity must not be {@literal null}. + */ + public GemfireQueryCreator(PartTree tree, GemfirePersistentEntity entity) { + + super(tree); + + this.query = new QueryBuilder(entity); + this.indexes = new IndexProvider(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#createQuery(org.springframework.data.domain.Sort) + */ + @Override + public QueryString createQuery(Sort dynamicSort) { + + this.indexes = new IndexProvider(); + return super.createQuery(dynamicSort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) + */ + @Override + protected Predicates create(Part part, Iterator iterator) { + return Predicates.create(part, this.indexes); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) + */ + @Override + protected Predicates and(Part part, Predicates base, Iterator iterator) { + return base.and(Predicates.create(part, this.indexes)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object) + */ + @Override + protected Predicates or(Predicates base, Predicates criteria) { + return base.or(criteria); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort) + */ + @Override + protected QueryString complete(Predicates criteria, Sort sort) { + + QueryString result = query.create(criteria); + + if (LOG.isDebugEnabled()) { + LOG.debug("Created query: " + result.toString()); + } + + return result; + } + + private static class IndexProvider implements Iterator { + + private int index; + + public IndexProvider() { + this.index = 1; + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#hasNext() + */ + @Override + public boolean hasNext() { + return index <= Integer.MAX_VALUE; + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#next() + */ + @Override + public Integer next() { + return index++; + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#remove() + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java new file mode 100644 index 00000000..cab19a3e --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java @@ -0,0 +1,86 @@ +/* + * 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.query; + +import java.lang.reflect.Method; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; +import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Gemfire specific {@link QueryMethod}. + * + * @author Oliver Gierke + */ +public class GemfireQueryMethod extends QueryMethod { + + private final Method method; + private final GemfirePersistentEntity entity; + + /** + * Creates a new {@link GemfireQueryMethod} from the given {@link Method} and {@link RepositoryMetadata}. + * + * @param method must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param context must not be {@literal null}. + */ + public GemfireQueryMethod(Method method, RepositoryMetadata metadata, + MappingContext, GemfirePersistentProperty> context) { + + super(method, metadata); + + Assert.notNull(context); + + this.method = method; + this.entity = context.getPersistentEntity(getDomainClass()); + } + + /** + * Returns whether the query method contains an annotated, non-empty query. + * + * @return + */ + public boolean hasAnnotatedQuery() { + return StringUtils.hasText(getAnnotatedQuery()); + } + + /** + * @return the entity + */ + public GemfirePersistentEntity getPersistentEntity() { + return entity; + } + + /** + * Returns the query annotated to the query method. + * + * @return the annotated query or {@literal null} in case it's empty or none available. + */ + String getAnnotatedQuery() { + + Query query = method.getAnnotation(Query.class); + String queryString = query == null ? null : (String) AnnotationUtils.getValue(query); + + return StringUtils.hasText(queryString) ? queryString : null; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireRepositoryQuery.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireRepositoryQuery.java new file mode 100644 index 00000000..0018bb5e --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireRepositoryQuery.java @@ -0,0 +1,50 @@ +/* + * 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.query; + +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.util.Assert; + +/** + * Base class for GemFire specific {@link RepositoryQuery} implementations. + * + * @author Oliver Gierke + */ +abstract class GemfireRepositoryQuery implements RepositoryQuery { + + private final GemfireQueryMethod queryMethod; + + /** + * Creates a new {@link GemfireRepositoryQuery} using the given {@link GemfireQueryMethod}. + * + * @param queryMethod must not be {@literal null}. + */ + public GemfireRepositoryQuery(GemfireQueryMethod queryMethod) { + + Assert.notNull(queryMethod); + this.queryMethod = queryMethod; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() + */ + @Override + public QueryMethod getQueryMethod() { + return this.queryMethod; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/PartTreeGemfireRepositoryQuery.java b/src/main/java/org/springframework/data/gemfire/repository/query/PartTreeGemfireRepositoryQuery.java new file mode 100644 index 00000000..56a1982f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/PartTreeGemfireRepositoryQuery.java @@ -0,0 +1,68 @@ +/* + * 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.query; + +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * {@link GemfireRepositoryQuery} backed by a {@link PartTree} and thus, deriving an OQL query from the backing query + * method's name. + * + * @author Oliver Gierke + */ +public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery { + + private final GemfireQueryMethod method; + private final PartTree tree; + private final GemfireTemplate template; + + /** + * Creates a new {@link PartTreeGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and + * {@link GemfireTemplate}. + * + * @param method must not be {@literal null}. + * @param template must not be {@literal null}. + */ + public PartTreeGemfireRepositoryQuery(GemfireQueryMethod method, GemfireTemplate template) { + + super(method); + + Class domainClass = method.getEntityInformation().getJavaType(); + + this.tree = new PartTree(method.getName(), domainClass); + this.method = method; + this.template = template; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) + */ + @Override + public Object execute(Object[] parameters) { + + ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(method.getParameters(), parameters); + QueryString query = new GemfireQueryCreator(tree, method.getPersistentEntity()).createQuery(parameterAccessor + .getSort()); + + RepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(query.toString(), method, template); + + return repositoryQuery.execute(parameters); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java b/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java new file mode 100644 index 00000000..c07b5412 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java @@ -0,0 +1,6 @@ +package org.springframework.data.gemfire.repository.query; + +interface Predicate { + + String toString(String alias); +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java b/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java new file mode 100644 index 00000000..8d42093d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java @@ -0,0 +1,173 @@ +/* + * 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.query; + +import java.util.Iterator; + +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.util.Assert; + +class Predicates implements Predicate { + + private final Predicate current; + + /** + * Creates a new {@link Predicates} wrapper instance. + * + * @param predicate must not be {@literal null}. + */ + private Predicates(Predicate predicate) { + this.current = predicate; + } + + private static Predicates create(Predicate predicate) { + return new Predicates(predicate); + } + + /** + * Creates a new Predicate for the given {@link Part} and index iterator. + * + * @param part must not be {@literal null}. + * @param value must not be {@literal null}. + * @return + */ + public static Predicates create(Part part, Iterator value) { + return create(new AtomicPredicate(part, value)); + } + + /** + * And-concatenates the given {@link Predicate} to the current one. + * + * @param predicate must not be {@literal null}. + * @return + */ + public Predicates and(final Predicate predicate) { + + return create(new Predicate() { + @Override + public String toString(String alias) { + return String.format("%s AND %s", Predicates.this.current.toString(alias), predicate.toString(alias)); + } + }); + } + + /** + * Or-concatenates the given {@link Predicate} to the current one. + * + * @param predicate must not be {@literal null}. + * @return + */ + public Predicates or(final Predicate predicate) { + + return create(new Predicate() { + @Override + public String toString(String alias) { + return String.format("%s OR %s", Predicates.this.current.toString(alias), predicate.toString(alias)); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.repository.query.Predicate#toString(java.lang.String) + */ + @Override + public String toString(String alias) { + return current.toString(alias); + } + + /** + * Predicate to create a predicate expression for a {@link Part}. + * + * @author Oliver Gierke + */ + public static class AtomicPredicate implements Predicate { + + private final Part part; + private final Iterator value; + + /** + * Creates a new {@link AtomicPredicate}. + * + * @param part must not be {@literal null}. + * @param value must not be {@literal null}. + */ + public AtomicPredicate(Part part, Iterator value) { + + Assert.notNull(part); + Assert.notNull(value); + + this.part = part; + this.value = value; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.repository.query.Predicate#toString(java.lang.String) + */ + @Override + public String toString(String alias) { + + Type type = part.getType(); + return String.format("%s.%s %s", alias == null ? QueryBuilder.DEFAULT_ALIAS : alias, part.getProperty() + .toDotPath(), toClause(type)); + } + + private String toClause(Type type) { + + switch (type) { + case IS_NULL: + case IS_NOT_NULL: + return String.format("%s NULL", getOperator(type)); + default: + return String.format("%s $%s", getOperator(type), value.next()); + } + } + + /** + * Maps the given {@link Type} to an OQL operator. + * + * @param type + * @return + */ + private String getOperator(Type type) { + + switch (type) { + case IN: + return "IN SET"; + case NOT_IN: + return "NOT IN SET"; + case GREATER_THAN: + return ">"; + case GREATER_THAN_EQUAL: + return ">="; + case LESS_THAN: + return "<"; + case LESS_THAN_EQUAL: + return "<="; + case IS_NOT_NULL: + case NEGATING_SIMPLE_PROPERTY: + return "!="; + case IS_NULL: + case SIMPLE_PROPERTY: + return "="; + default: + throw new IllegalArgumentException(String.format("Unsupported operator %s!", type)); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java new file mode 100644 index 00000000..3d1978f9 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java @@ -0,0 +1,53 @@ +/* + * 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.query; + +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.util.Assert; + +/** + * + * @author Oliver Gierke + */ +class QueryBuilder { + + static final String DEFAULT_ALIAS = "x"; + + private final String query; + + public QueryBuilder(String source) { + Assert.hasText(source); + this.query = source; + } + + public QueryBuilder(GemfirePersistentEntity entity) { + this(String.format("SELECT * FROM /%s %s", entity.getRegionName(), DEFAULT_ALIAS)); + } + + public QueryString create(Predicate predicate) { + + return new QueryString(query + " WHERE " + predicate.toString(DEFAULT_ALIAS)); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return query; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java new file mode 100644 index 00000000..ee4ca7cc --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java @@ -0,0 +1,118 @@ +/* + * 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.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.gemstone.gemfire.cache.Region; + +/** + * Value object to work with OQL query strings. + * + * @author Oliver Gierke + */ +class QueryString { + + private static final String REGION_PATTERN = "(?<=\\/)%s"; + private static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d"; + private static final String IN_PATTERN = "(?<=IN (SET|LIST) )\\$\\d"; + + private final String query; + + /** + * Creates a {@link QueryString} from the given {@link String} query. + * + * @param source + */ + public QueryString(String source) { + + Assert.hasText(source); + this.query = source; + } + + /** + * Creates a {@literal SELECT} query for the given domain class. + * + * @param domainClass must not be {@literal null}. + */ + public QueryString(Class domainClass) { + this(String.format("SELECT * FROM /%s", domainClass.getSimpleName())); + } + + /** + * Replaces the domain classes referenced inside the current query with the given {@link Region}. + * + * @param domainClass must not be {@literal null}. + * @param region must not be {@literal null}. + * @return + */ + public QueryString forRegion(Class domainClass, Region region) { + + String pattern = String.format(REGION_PATTERN, domainClass.getSimpleName()); + return new QueryString(query.replaceAll(pattern, region.getName())); + } + + /** + * Binds the given values to the {@literal IN} parameter keyword by expanding the given values into a comma-separated + * {@link String}. + * + * @param values the values to bind, returns the {@link QueryString} as is if {@literal null} is given. + * @return + */ + public QueryString bindIn(Collection values) { + + if (values == null) { + return this; + } + + String valueString = StringUtils.collectionToDelimitedString(values, ", ", "'", "'"); + return new QueryString(query.replaceFirst(IN_PATTERN, String.format("(%s)", valueString))); + } + + /** + * Returns the parameter indexes used in this query. + * + * @return the parameter indexes used in this query or an empty {@link Iterable} if none are used. + */ + public Iterable getInParameterIndexes() { + + Pattern pattern = Pattern.compile(IN_PARAMETER_PATTERN); + Matcher matcher = pattern.matcher(query); + List result = new ArrayList(); + + while (matcher.find()) { + result.add(Integer.parseInt(matcher.group())); + } + + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return query; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java new file mode 100644 index 00000000..2c417943 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java @@ -0,0 +1,103 @@ +/* + * 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.query; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * {@link GemfireRepositoryQuery} using plain {@link String} based OQL queries. + * + * @author Oliver Gierke + */ +public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { + + private final QueryString query; + private final GemfireQueryMethod method; + private final GemfireTemplate template; + + /** + * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and + * {@link GemfireTemplate}. The actual query {@link String} will be looked up from the query method. + * + * @param method must not be {@literal null}. + * @param template must not be {@literal null}. + */ + public StringBasedGemfireRepositoryQuery(GemfireQueryMethod method, GemfireTemplate template) { + this(method.getAnnotatedQuery(), method, template); + } + + /** + * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given query {@link String}, + * {@link GemfireQueryMethod} and {@link GemfireTemplate}. + * + * @param query will fall back to the query annotated to the given {@link GemfireQueryMethod} if {@literal null} is + * given. + * @param method must not be {@literal null}. + * @param template must not be {@literal null}. + */ + public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod method, GemfireTemplate template) { + + super(method); + + Assert.notNull(template); + + this.query = new QueryString(StringUtils.hasText(query) ? query : method.getAnnotatedQuery()); + this.method = method; + this.template = template; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) + */ + @Override + public Object execute(Object[] parameters) { + + ParametersParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), parameters); + QueryString query = this.query.forRegion(method.getEntityInformation().getJavaType(), template.getRegion()); + + Iterator indexes = query.getInParameterIndexes().iterator(); + while (indexes.hasNext()) { + query = query.bindIn(toCollection(accessor.getBindableValue(indexes.next() - 1))); + } + + return template.find(query.toString(), parameters); + } + + /** + * Returns the given object as collection. Collections will be returned as is, Arrays will be converted into a + * collection and all other objects will be wrapped into a single-element collection. + * + * @param source + * @return + */ + private Collection toCollection(Object source) { + + if (source instanceof Collection) { + return (Collection) source; + } + + return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java new file mode 100644 index 00000000..cafb940a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java @@ -0,0 +1,147 @@ +/* + * 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.support; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; +import org.springframework.data.gemfire.mapping.Regions; +import org.springframework.data.gemfire.repository.query.DefaultGemfireEntityInformation; +import org.springframework.data.gemfire.repository.query.GemfireEntityInformation; +import org.springframework.data.gemfire.repository.query.GemfireQueryMethod; +import org.springframework.data.gemfire.repository.query.PartTreeGemfireRepositoryQuery; +import org.springframework.data.gemfire.repository.query.StringBasedGemfireRepositoryQuery; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +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 com.gemstone.gemfire.cache.Region; + +/** + * {@link RepositoryFactorySupport} implementation creating repository proxies for Gemfire. + * + * @author Oliver Gierke + */ +public class GemfireRepositoryFactory extends RepositoryFactorySupport { + + private final MappingContext, GemfirePersistentProperty> context; + private final Regions regions; + + /** + * Creates a new {@link GemfireRepositoryFactory}. + * + * @param regions must not be {@literal null}. + * @param context + */ + public GemfireRepositoryFactory(Iterable> regions, + MappingContext, GemfirePersistentProperty> context) { + + Assert.notNull(regions); + + this.context = context == null ? new GemfireMappingContext() : context; + this.regions = new Regions(regions, this.context); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) + */ + @Override + @SuppressWarnings("unchecked") + public GemfireEntityInformation getEntityInformation(Class domainClass) { + + GemfirePersistentEntity entity = (GemfirePersistentEntity) context.getPersistentEntity(domainClass); + return new DefaultGemfireEntityInformation(entity); + } + + /* + * (non-Javadoc) + * @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 entityInformation = getEntityInformation(metadata.getDomainClass()); + GemfireTemplate gemfireTemplate = getTemplate(metadata); + + return new SimpleGemfireRepository(gemfireTemplate, entityInformation); + } + + private GemfireTemplate getTemplate(RepositoryMetadata metadata) { + + Class domainClass = metadata.getDomainClass(); + Region region = regions.getRegion(domainClass); + + GemfirePersistentEntity entity = context.getPersistentEntity(domainClass); + + Class regionKeyType = region.getAttributes().getKeyConstraint(); + Class entityIdType = metadata.getIdClass(); + + 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)); + } + + return new GemfireTemplate(region); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata) + */ + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { + return SimpleGemfireRepository.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key) + */ + @Override + protected QueryLookupStrategy getQueryLookupStrategy(Key key) { + return new QueryLookupStrategy() { + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { + + GemfireQueryMethod queryMethod = new GemfireQueryMethod(method, metadata, context); + GemfireTemplate template = getTemplate(metadata); + + if (queryMethod.hasAnnotatedQuery()) { + return new StringBasedGemfireRepositoryQuery(queryMethod, template); + } + + String namedQueryName = queryMethod.getNamedQueryName(); + if (namedQueries.hasQuery(namedQueryName)) { + return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(namedQueryName), queryMethod, template); + } + + return new PartTreeGemfireRepositoryQuery(queryMethod, template); + } + }; + } +} 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 new file mode 100644 index 00000000..1dfa5079 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java @@ -0,0 +1,70 @@ +/* + * 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.support; + +import java.io.Serializable; +import java.util.Collection; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; + +import com.gemstone.bp.edu.emory.mathcs.backport.java.util.Collections; +import com.gemstone.gemfire.cache.Region; + +/** + * + * @author Oliver Gierke + */ +public class GemfireRepositoryFactoryBean, S, ID extends Serializable> extends + RepositoryFactoryBeanSupport implements ApplicationContextAware { + + private MappingContext, GemfirePersistentProperty> context; + private Iterable> regions; + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Collection regions = applicationContext.getBeansOfType(Region.class).values(); + this.regions = Collections.unmodifiableCollection(regions); + } + + /** + * @param context the context to set + */ + public void setMappingContext(MappingContext, GemfirePersistentProperty> context) { + this.context = context; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createRepositoryFactory() + */ + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return new GemfireRepositoryFactory(regions, context); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepository.java b/src/main/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepository.java new file mode 100644 index 00000000..65b8b5b0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepository.java @@ -0,0 +1,174 @@ +package org.springframework.data.gemfire.repository.support; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.data.gemfire.GemfireCallback; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.repository.GemfireRepository; +import org.springframework.data.gemfire.repository.Wrapper; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.GemFireCheckedException; +import com.gemstone.gemfire.GemFireException; +import com.gemstone.gemfire.cache.Region; + +/** + * Basic repository implementation. + * + * @author Oliver Gierke + */ +public class SimpleGemfireRepository implements GemfireRepository { + + private final GemfireTemplate template; + private final EntityInformation entityInformation; + + /** + * Creates a new {@link SimpleGemfireRepository}. + * + * @param template must not be {@literal null}. + * @param entityInformation must not be {@literal null}. + */ + public SimpleGemfireRepository(GemfireTemplate template, EntityInformation entityInformation) { + + Assert.notNull(template); + Assert.notNull(entityInformation); + + this.template = template; + this.entityInformation = entityInformation; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#save(java.lang.Object) + */ + public T save(T entity) { + template.put(entityInformation.getId(entity), entity); + return entity; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#save(java.lang.Iterable) + */ + public Iterable save(Iterable entities) { + List result = new ArrayList(); + for (T entity : entities) { + result.add(save(entity)); + } + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#findOne(java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public T findOne(ID id) { + Object object = template.get(id); + return (T) object; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#exists(java.io.Serializable) + */ + public boolean exists(ID id) { + return findOne(id) != null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#findAll() + */ + public Collection findAll() { + return template.execute(new GemfireCallback>() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Collection doInGemfire(Region region) { + return region.values(); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) + */ + @Override + @SuppressWarnings("unchecked") + public Collection findAll(Iterable ids) { + + List parameters = new ArrayList(); + for (ID id : ids) { + parameters.add(id); + } + + return (Collection) template.getAll(parameters).values(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#count() + */ + public long count() { + return template.execute(new GemfireCallback() { + @SuppressWarnings("rawtypes") + public Long doInGemfire(Region region) throws GemFireCheckedException, GemFireException { + return Long.valueOf(region.keySet().size()); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#delete(java.lang.Object) + */ + public void delete(T entity) { + template.remove(entityInformation.getId(entity)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#delete(java.lang.Iterable) + */ + public void delete(Iterable entities) { + for (T entity : entities) { + delete(entity); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.Repository#deleteAll() + */ + public void deleteAll() { + + template.execute(new GemfireCallback() { + @SuppressWarnings("rawtypes") + public Void doInGemfire(Region region) { + region.clear(); + return null; + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) + */ + public void delete(ID id) { + template.remove(id); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.gemfire.repository.GemfireRepository#save(org.springframework.data.gemfire.repository.Wrapper) + */ + @Override + public T save(Wrapper wrapper) { + return template.put(wrapper.getKey(), wrapper.getEntity()); + } +} diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas index 2bb1ca58..5bff7b59 100644 --- a/src/main/resources/META-INF/spring.schemas +++ b/src/main/resources/META-INF/spring.schemas @@ -1,3 +1,4 @@ http\://www.springframework.org/schema/gemfire/spring-gemfire-1.0.xsd=org/springframework/data/gemfire/config/spring-gemfire-1.0.xsd http\://www.springframework.org/schema/gemfire/spring-gemfire-1.1.xsd=org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd -http\://www.springframework.org/schema/gemfire/spring-gemfire.xsd=org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd \ No newline at end of file +http\://www.springframework.org/schema/gemfire/spring-gemfire-1.2.xsd=org/springframework/data/gemfire/config/spring-gemfire-1.2.xsd +http\://www.springframework.org/schema/gemfire/spring-gemfire.xsd=org/springframework/data/gemfire/config/spring-gemfire-1.2.xsd \ No newline at end of file diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.2.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.2.xsd new file mode 100644 index 00000000..70ab629b --- /dev/null +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.2.xsd @@ -0,0 +1,1309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + namespace and its 'properties' element. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The reference to a GemfireTemplate. Will default to 'gemfireTemplate'. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java new file mode 100644 index 00000000..8515c585 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/mapping/GemfirePersistentEntityUnitTests.java @@ -0,0 +1,66 @@ +/* + * 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.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.util.ClassTypeInformation; + +/** + * Unit tests for {@link GemfirePersistentEntity}. + * + * @author Oliver Gierke + */ +public class GemfirePersistentEntityUnitTests { + + @Test + public void defaultsRegionNameToClassName() { + GemfirePersistentEntity entity = new GemfirePersistentEntity( + ClassTypeInformation.from(UnannotatedRegion.class)); + assertThat(entity.getRegionName(), is(UnannotatedRegion.class.getSimpleName())); + } + + @Test + public void defaultsAnnotatedRegionToCLassName() { + GemfirePersistentEntity entity = new GemfirePersistentEntity( + ClassTypeInformation.from(UnnamedRegion.class)); + assertThat(entity.getRegionName(), is(UnnamedRegion.class.getSimpleName())); + } + + @Test + public void readsRegionNameFromAnnotation() { + + GemfirePersistentEntity entity = new GemfirePersistentEntity( + ClassTypeInformation.from(AnnotatedRegion.class)); + assertThat(entity.getRegionName(), is("Foo")); + } + + static class UnannotatedRegion { + + } + + @Region("Foo") + static class AnnotatedRegion { + + } + + @Region + static class UnnamedRegion { + + } +} diff --git a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTest.java new file mode 100644 index 00000000..92553a30 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerIntegrationTest.java @@ -0,0 +1,81 @@ +/* + * 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.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.MappingPdxSerializer; +import org.springframework.data.gemfire.repository.sample.Address; +import org.springframework.data.gemfire.repository.sample.Person; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheFactory; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionFactory; + +/** + * Integration tests for {@link MappingPdxSerializer}. + * + * @author Oliver Gierke + */ +public class MappingPdxSerializerIntegrationTest { + + Region region; + + @Before + public void setUp() { + + MappingPdxSerializer serializer = new MappingPdxSerializer(new GemfireMappingContext(), + new DefaultConversionService()); + + CacheFactory factory = new CacheFactory(); + factory.setPdxSerializer(serializer); + factory.setPdxPersistent(true); + + Cache cache = factory.create(); + + RegionFactory regionFactory = cache.createRegionFactory(); + regionFactory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE); + region = regionFactory.create("foo"); + } + + @Test + public void serializeAndDeserializeCorrectly() { + + Address address = new Address(); + address.zipCode = "01234"; + address.city = "London"; + + Person person = new Person(1L, "Oliver", "Gierke"); + person.address = address; + + region.put(1L, person); + Object result = region.get(1L); + + assertThat(result instanceof Person, is(true)); + + Person reference = person; + assertThat(reference.getFirstname(), is(person.getFirstname())); + assertThat(reference.getLastname(), is(person.getLastname())); + assertThat(reference.address, is(person.address)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java new file mode 100644 index 00000000..a6f43ace --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/mapping/MappingPdxSerializerUnitTests.java @@ -0,0 +1,81 @@ +/* + * 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.mapping; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.mapping.model.ParameterValueProvider; + +import com.gemstone.gemfire.pdx.PdxReader; + +/** + * Unit tests for {@link MappingPdxSerializer}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class MappingPdxSerializerUnitTests { + + GemfireMappingContext context; + ConversionService conversionService; + MappingPdxSerializer serializer; + + @Mock + EntityInstantiator instantiator; + @Mock + PdxReader reader; + + @Before + public void setUp() { + + context = new GemfireMappingContext(); + conversionService = new GenericConversionService(); + serializer = new MappingPdxSerializer(context, conversionService); + } + + @Test + @SuppressWarnings("unchecked") + public void usesRegisteredInstantiator() { + + Person person = new Person(1L, "Oliver", "Gierke"); + + ParameterValueProvider provider = any(ParameterValueProvider.class); + GemfirePersistentEntity entity = any(GemfirePersistentEntity.class); + when(instantiator.createInstance(entity, provider)).thenReturn(person); + + Map, EntityInstantiator> instantiators = new HashMap, EntityInstantiator>(); + instantiators.put(Person.class, instantiator); + + serializer.setGemfireInstantiators(instantiators); + serializer.fromData(Person.class, reader); + + verify(instantiator, times(1)).createInstance(eq(context.getPersistentEntity(Person.class)), + any(ParameterValueProvider.class)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessorUnitTests.java b/src/test/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessorUnitTests.java new file mode 100644 index 00000000..5bfffdde --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/mapping/PdxReaderPropertyAccessorUnitTests.java @@ -0,0 +1,85 @@ +/* + * 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.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.TypedValue; + +import com.gemstone.bp.edu.emory.mathcs.backport.java.util.Arrays; +import com.gemstone.gemfire.pdx.PdxReader; + +/** + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class PdxReaderPropertyAccessorUnitTests { + + @Mock + PdxReader reader; + + @Test + @SuppressWarnings("unchecked") + public void appliesToPdxReadersOnly() { + List> classes = Arrays.asList(PdxReaderPropertyAccessor.INSTANCE.getSpecificTargetClasses()); + assertThat(classes, hasItem(PdxReader.class)); + } + + @Test + public void canReadPropertyIfReaderHasField() { + + when(reader.hasField("key")).thenReturn(true); + assertThat(PdxReaderPropertyAccessor.INSTANCE.canRead(null, reader, "key"), is(true)); + + when(reader.hasField("key")).thenReturn(false); + assertThat(PdxReaderPropertyAccessor.INSTANCE.canRead(null, reader, "key"), is(false)); + } + + @Test + public void returnsTypedNullIfNullIsReadFromReader() { + + when(reader.readObject("key")).thenReturn(null); + assertThat(PdxReaderPropertyAccessor.INSTANCE.read(null, reader, "key"), is(TypedValue.NULL)); + } + + @Test + public void returnsTypeValueWithValueReadFromReader() { + + when(reader.readObject("key")).thenReturn("String"); + + TypedValue result = PdxReaderPropertyAccessor.INSTANCE.read(null, reader, "key"); + + assertThat(result.getTypeDescriptor(), is(TypeDescriptor.valueOf(String.class))); + assertThat(result.getValue(), is((Object) "String")); + } + + @Test(expected = UnsupportedOperationException.class) + public void doesNotSupportWrites() { + + assertThat(PdxReaderPropertyAccessor.INSTANCE.canWrite(null, null, null), is(false)); + PdxReaderPropertyAccessor.INSTANCE.write(null, null, null, reader); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/config/NamespaceRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/repository/config/NamespaceRepositoryIntegrationTests.java new file mode 100644 index 00000000..8b75f00d --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/config/NamespaceRepositoryIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * 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.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.gemfire.mapping.Regions; +import org.springframework.data.gemfire.repository.sample.PersonRepository; +import org.springframework.data.gemfire.repository.support.AbstractGemfireRepositoryFactoryIntegrationTests; +import org.springframework.test.context.ContextConfiguration; + +/** + * Integration tests for namespace usage. + * + * @author Oliver Gierke + */ +@ContextConfiguration("repo-context.xml") +public class NamespaceRepositoryIntegrationTests extends AbstractGemfireRepositoryFactoryIntegrationTests { + + @Autowired + PersonRepository repository; + + @Override + protected PersonRepository getRepository(Regions regions) { + return repository; + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java new file mode 100644 index 00000000..95d673c8 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java @@ -0,0 +1,54 @@ +/* + * 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.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * Unit tests for {@link GemfireQueryCreator}. + * + * @author Oliver Gierke + */ +public class GemfireQueryCreatorUnitTests { + + GemfirePersistentEntity entity; + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + + GemfireMappingContext context = new GemfireMappingContext(); + entity = (GemfirePersistentEntity) context.getPersistentEntity(Person.class); + } + + @Test + public void createsQueryForSimplePropertyReferenceCorrectly() { + + PartTree partTree = new PartTree("findByFirstname", Person.class); + GemfireQueryCreator creator = new GemfireQueryCreator(partTree, entity); + + QueryString query = creator.createQuery(); + assertThat(query.toString(), is("SELECT * FROM /simple x WHERE x.firstname = $1")); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java new file mode 100644 index 00000000..f0b7d64b --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java @@ -0,0 +1,75 @@ +/* + * 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.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.lang.reflect.Method; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.repository.core.RepositoryMetadata; + +/** + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class GemfireQueryMethodUnitTests { + + @Mock + RepositoryMetadata metadata; + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void detectsAnnotatedQueryCorrectly() throws Exception { + + GemfireMappingContext context = new GemfireMappingContext(); + when(metadata.getDomainClass()).thenReturn((Class) Person.class); + when(metadata.getReturnedDomainClass(Mockito.any(Method.class))).thenReturn((Class) Person.class); + + GemfireQueryMethod method = new GemfireQueryMethod(Sample.class.getMethod("annotated"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(true)); + assertThat(method.getAnnotatedQuery(), is("foo")); + + method = new GemfireQueryMethod(Sample.class.getMethod("annotatedButEmpty"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(false)); + assertThat(method.getAnnotatedQuery(), is(nullValue())); + + method = new GemfireQueryMethod(Sample.class.getMethod("notAnnotated"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(false)); + assertThat(method.getAnnotatedQuery(), is(nullValue())); + } + + interface Sample { + + @Query("foo") + void annotated(); + + @Query("") + void annotatedButEmpty(); + + void notAnnotated(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java new file mode 100644 index 00000000..5bf25632 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java @@ -0,0 +1,76 @@ +/* + * 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.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Iterator; + +import org.junit.Test; +import org.springframework.data.gemfire.repository.query.Predicates.AtomicPredicate; +import org.springframework.data.repository.query.parser.Part; + +/** + * + * @author Oliver Gierke + */ +public class PredicatesUnitTests { + + @Test + public void atomicPredicateDefaultsAlias() { + + Part part = new Part("firstname", Person.class); + + Iterable indexes = Arrays.asList(1); + + Predicate predicate = new AtomicPredicate(part, indexes.iterator()); + assertThat(predicate.toString(null), is("x.firstname = $1")); + } + + @Test + public void concatenatesAndPredicateCorrectly() { + + Part left = new Part("firstname", Person.class); + Part right = new Part("lastname", Person.class); + Iterator indexes = Arrays.asList(1, 2).iterator(); + + Predicates predicate = Predicates.create(left, indexes); + predicate = predicate.and(new AtomicPredicate(right, indexes)); + + assertThat(predicate.toString(null), is("x.firstname = $1 AND x.lastname = $2")); + } + + @Test + public void concatenatesOrPredicateCorrectly() { + + Part left = new Part("firstname", Person.class); + Part right = new Part("lastname", Person.class); + Iterator indexes = Arrays.asList(1, 2).iterator(); + + Predicates predicate = Predicates.create(left, indexes); + predicate = predicate.or(new AtomicPredicate(right, indexes)); + + assertThat(predicate.toString(null), is("x.firstname = $1 OR x.lastname = $2")); + } + + static class Person { + + String firstname; + String lastname; + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java new file mode 100644 index 00000000..dc8daaec --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java @@ -0,0 +1,68 @@ +/* + * 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.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.gemfire.repository.sample.Person; + +import com.gemstone.gemfire.cache.Region; + +/** + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class QueryStringUnitTests { + + @Mock + @SuppressWarnings("rawtypes") + Region region; + + @Test + public void replacesDomainObjectWithRegionNameCorrectly() { + + QueryString query = new QueryString("SELECT * FROM /Person p WHERE p.firstname = $1"); + + when(region.getName()).thenReturn("foo"); + assertThat(query.forRegion(Person.class, region).toString(), is("SELECT * FROM /foo p WHERE p.firstname = $1")); + } + + @Test + public void bindsInValuesCorrectly() { + + QueryString query = new QueryString("SELECT * FROM /Person p WHERE p.firstname IN SET $1"); + List values = Arrays.asList(1, 2, 3); + assertThat(query.bindIn(values).toString(), is("SELECT * FROM /Person p WHERE p.firstname IN SET ('1', '2', '3')")); + } + + @Test + public void detectsInParameterIndexesCorrectly() { + + QueryString query = new QueryString("IN SET $1 OR IN SET $2"); + Iterable indexes = query.getInParameterIndexes(); + assertThat(indexes, is((Iterable) Arrays.asList(1, 2))); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java new file mode 100644 index 00000000..0de38379 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Address.java @@ -0,0 +1,26 @@ +/* + * 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.sample; + +/** + * + * @author Oliver Gierke + */ +public class Address { + + public String zipCode; + public String city; +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java new file mode 100644 index 00000000..372b9ee5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/Person.java @@ -0,0 +1,57 @@ +/* + * 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.sample; + +import java.io.Serializable; + +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.mapping.Region; + +/** + * + * @author Oliver Gierke + */ +@Region("simple") +public class Person implements Serializable { + + private static final long serialVersionUID = 508843183613325255L; + + @Id + public Long id; + public String firstname; + public String lastname; + public Address address; + + public Person(Long id, String firstname, String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return firstname; + } + + /** + * @return the lastname + */ + public String getLastname() { + return lastname; + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java new file mode 100644 index 00000000..7fe55fd2 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java @@ -0,0 +1,45 @@ +/* + * 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.sample; + +import java.util.Collection; + +import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.repository.Repository; + +/** + * + * + * @author Oliver Gierke + */ +public interface PersonRepository extends Repository { + + @Query("SELECT * FROM /Person p WHERE p.firstname = $1") + Collection findByFirstnameAnnotated(String firstname); + + @Query("SELECT * FROM /Person p WHERE p.firstname IN SET $1") + Collection findByFirstnamesAnnotated(Collection firstnames); + + Collection findByFirstname(String firstname); + + Collection findByFirstnameIn(Collection firstnames); + + Collection findByFirstnameIn(String... firstnames); + + Collection findByFirstnameAndLastname(String firstname, String lastname); + + Collection findByFirstnameOrLastname(String firstname, String lastname); +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/support/AbstractGemfireRepositoryFactoryIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/repository/support/AbstractGemfireRepositoryFactoryIntegrationTests.java new file mode 100644 index 00000000..c53ab4f4 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/support/AbstractGemfireRepositoryFactoryIntegrationTests.java @@ -0,0 +1,120 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.mapping.Regions; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.gemfire.repository.sample.PersonRepository; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.Region; + +/** + * Integration test for {@link GemfireRepositoryFactory}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +public abstract class AbstractGemfireRepositoryFactoryIntegrationTests { + + @Autowired + List> regions; + + Person dave, carter, boyd, stefan, leroi, jeff; + PersonRepository repository; + + @Before + public void setUp() { + + dave = new Person(1L, "Dave", "Matthews"); + carter = new Person(2L, "Carter", "Beauford"); + boyd = new Person(3L, "Boyd", "Tinsley"); + stefan = new Person(4L, "Stefan", "Lessard"); + leroi = new Person(5L, "Leroi", "Moore"); + jeff = new Person(6L, "Jeff", "Coffin"); + + GemfireMappingContext context = new GemfireMappingContext(); + + Regions regions = new Regions(this.regions, context); + GemfireTemplate template = new GemfireTemplate(regions.getRegion(Person.class)); + + template.put(dave.id, dave); + template.put(carter.id, carter); + template.put(boyd.id, boyd); + template.put(stefan.id, stefan); + template.put(leroi.id, leroi); + template.put(jeff.id, jeff); + + repository = getRepository(regions); + } + + protected abstract PersonRepository getRepository(Regions regions); + + @Test + public void foo() { + assertResultsFound(repository.findByFirstnameAnnotated("Dave"), dave); + } + + @Test + public void executesAnnotatedInQueryMethodCorrectly() { + assertResultsFound(repository.findByFirstnamesAnnotated(Arrays.asList("Carter", "Dave")), carter, dave); + } + + @Test + public void executesInQueryMethodCorrectly() { + assertResultsFound(repository.findByFirstnameIn(Arrays.asList("Carter", "Dave")), carter, dave); + } + + @Test + public void executesDerivedQueryCorrectly() { + assertResultsFound(repository.findByFirstname("Carter"), carter); + assertResultsFound(repository.findByFirstnameIn(Arrays.asList("Stefan", "Boyd")), stefan, boyd); + assertResultsFound(repository.findByFirstnameIn("Leroi"), leroi); + } + + @Test + public void executesDerivedQueryWithAndCorrectly() { + assertResultsFound(repository.findByFirstnameAndLastname("Carter", "Beauford"), carter); + } + + @Test + public void executesDerivedQueryWithOrCorrectly() { + assertResultsFound(repository.findByFirstnameOrLastname("Carter", "Matthews"), carter, dave); + } + + private void assertResultsFound(Collection result, T... expected) { + + assertThat(result, is(notNullValue())); + assertThat(result.size(), is(expected.length)); + + for (T element : expected) { + assertThat(result.contains(element), is(true)); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryIntegrationTests.java new file mode 100644 index 00000000..bd31ad4a --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryIntegrationTests.java @@ -0,0 +1,36 @@ +/* + * 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.support; + +import org.springframework.data.gemfire.mapping.Regions; +import org.springframework.data.gemfire.repository.sample.PersonRepository; +import org.springframework.test.context.ContextConfiguration; + +/** + * Integration test for {@link GemfireRepositoryFactory}. + * + * @author Oliver Gierke + */ +@ContextConfiguration("../config/repo-context.xml") +public class GemfireRepositoryFactoryIntegrationTests extends AbstractGemfireRepositoryFactoryIntegrationTests { + + @Override + protected PersonRepository getRepository(Regions regions) { + + GemfireRepositoryFactory factory = new GemfireRepositoryFactory(regions, null); + return factory.getRepository(PersonRepository.class); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepositoryIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepositoryIntegrationTest.java new file mode 100644 index 00000000..d2360998 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/support/SimpleGemfireRepositoryIntegrationTest.java @@ -0,0 +1,104 @@ +/* + * 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.support; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.repository.sample.Person; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.support.ReflectionEntityInformation; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.query.SelectResults; + +/** + * Integration tests for {@link SimpleGemfireRepository}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("../../basic-template.xml") +public class SimpleGemfireRepositoryIntegrationTest { + + @Autowired + GemfireTemplate template; + + SimpleGemfireRepository repository; + + @Before + public void setUp() { + + EntityInformation information = new ReflectionEntityInformation(Person.class); + repository = new SimpleGemfireRepository(template, information); + } + + @Test + public void storeAndDeleteEntity() { + + Person person = new Person(1L, "Oliver", "Gierke"); + + repository.save(person); + + assertThat(repository.count(), is(1L)); + assertThat(repository.findOne(person.id), is(person)); + assertThat(repository.findAll().size(), is(1)); + + repository.delete(person); + + assertThat(repository.count(), is(0L)); + assertThat(repository.findOne(person.id), is(nullValue())); + assertThat(repository.findAll().size(), is(0)); + } + + @Test + public void queryRegion() throws Exception { + + Person person = new Person(1L, "Oliver", "Gierke"); + + template.put(1L, person); + + SelectResults persons = template.find("SELECT * FROM /simple s WHERE s.firstname = $1", person.firstname); + + assertThat(persons.size(), is(1)); + assertThat(persons.iterator().next(), is(person)); + } + + @Test + public void findAllWithGivenIds() { + + Person dave = new Person(1L, "Dave", "Matthews"); + Person carter = new Person(2L, "Carter", "Beauford"); + Person leroi = new Person(3L, "Leroi", "Moore"); + + template.put(dave.id, dave); + template.put(carter.id, carter); + template.put(leroi.id, leroi); + + Collection result = repository.findAll(Arrays.asList(carter.id, leroi.id)); + assertThat(result, hasItems(carter, leroi)); + assertThat(result, not(hasItems(dave))); + } +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 1f7cee2f..5b8659e2 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -5,6 +5,7 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n log4j.category.org.springframework.data.gemfire.listener=TRACE +log4j.category.org.springframework.data.gemfire.repository=DEBUG # for debugging datasource initialization # log4j.category.test.jdbc=DEBUG diff --git a/src/test/resources/org/springframework/data/gemfire/repository/config/repo-context.xml b/src/test/resources/org/springframework/data/gemfire/repository/config/repo-context.xml new file mode 100644 index 00000000..6c8c4a44 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/repository/config/repo-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/template.mf b/template.mf index fff33d8c..d29cfb28 100644 --- a/template.mf +++ b/template.mf @@ -12,6 +12,8 @@ Import-Template: org.springframework.context.*;version=${spring.range}, org.springframework.core.*;version=${spring.range}, org.springframework.dao.*;version=${spring.range}, + org.springframework.data.*;version="${springDataCommonsVersion:[=.=.=.=,+1.0.0)}", + org.springframework.expression.*;version="${springVersion:[=.=.=,+1.0.0)}", org.springframework.util.*;version=${spring.range}, org.springframework.transaction.*;version=${spring.range}, com.gemstone.gemfire.*;version=${gemfire.range},