/*
* Copyright 2010-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.gemfire.function;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.PdxInstanceFactory;
import org.apache.geode.pdx.PdxReader;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.geode.pdx.PdxWriter;
import org.apache.geode.pdx.internal.PdxInstanceEnum;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.gemfire.fork.ServerProcess;
import org.springframework.data.gemfire.fork.SpringContainerProcess;
import org.springframework.data.gemfire.function.annotation.GemfireFunction;
import org.springframework.data.gemfire.function.sample.ApplicationDomainFunctionExecutions;
import org.springframework.data.gemfire.process.ProcessWrapper;
import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* The ClientCacheFunctionExecutionWithPdxIntegrationTest class is a test suite of test cases testing Spring Data
* GemFire's Function annotation support and interaction between a GemFire client and server Cache
* when PDX is configured and read-serialized is set to true.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see SpringContainerProcess
* @see org.springframework.data.gemfire.function.annotation.GemfireFunction
* @see org.springframework.data.gemfire.function.sample.ApplicationDomainFunctionExecutions
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.pdx.PdxInstance
* @see org.apache.geode.pdx.PdxSerializer
* @see org.apache.geode.pdx.internal.PdxInstanceEnum
* @since 1.5.2
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@SuppressWarnings("unused")
public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientServerIntegrationTestsSupport {
private static ProcessWrapper gemfireServer;
@Autowired
private ClientCache gemfireClientCache;
@Autowired
private ApplicationDomainFunctionExecutions functionExecutions;
@BeforeClass
public static void startGemFireServer() throws Exception {
int availablePort = findAvailablePort();
gemfireServer = run(ServerProcess.class,
String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort),
getServerContextXmlFileLocation(ClientCacheFunctionExecutionWithPdxIntegrationTest.class));
waitForServerToStart(DEFAULT_HOSTNAME, availablePort);
System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort));
}
@AfterClass
public static void stopGemFireServer() {
System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY);
stop(gemfireServer);
}
protected PdxInstance toPdxInstance(final Map<String, Object> pdxData) {
PdxInstanceFactory pdxInstanceFactory = gemfireClientCache.createPdxInstanceFactory(pdxData.get("@type").toString());
for (Map.Entry<String, Object> entry : pdxData.entrySet()) {
pdxInstanceFactory.writeObject(entry.getKey(), entry.getValue());
}
return pdxInstanceFactory.create();
}
@Test
public void testConvertedFunctionArgumentTypes() {
Class[] argumentTypes = functionExecutions.captureConvertedArgumentTypes("test", 1, Boolean.TRUE,
new Person("Jon", "Doe"), Gender.MALE);
assertNotNull(argumentTypes);
assertEquals(5, argumentTypes.length);
assertEquals(String.class, argumentTypes[0]);
assertEquals(Integer.class, argumentTypes[1]);
assertEquals(Boolean.class, argumentTypes[2]);
assertEquals(Person.class, argumentTypes[3]);
assertEquals(Gender.class, argumentTypes[4]);
}
@Test
public void testUnconvertedFunctionArgumentTypes() {
Class[] argumentTypes = functionExecutions.captureUnconvertedArgumentTypes("test", 2, Boolean.FALSE,
new Person("Jane", "Doe"), Gender.FEMALE);
assertNotNull(argumentTypes);
assertEquals(5, argumentTypes.length);
assertEquals(String.class, argumentTypes[0]);
assertEquals(Integer.class, argumentTypes[1]);
assertEquals(Boolean.class, argumentTypes[2]);
assertTrue(PdxInstance.class.isAssignableFrom(argumentTypes[3]));
assertEquals(PdxInstanceEnum.class, argumentTypes[4]);
}
@Test
public void testGetAddressFieldValue() {
assertEquals("Portland", functionExecutions.getAddressField(new Address(
"100 Main St.", "Portland", "OR", "97205"), "city"));
}
@Test
public void testPdxDataFieldValue() {
Map<String, Object> pdxData = new HashMap<String, Object>(3);
pdxData.put("@type", "x.y.z.domain.MyApplicationDomainType");
pdxData.put("booleanField", Boolean.TRUE);
pdxData.put("integerField", 123);
pdxData.put("stringField", "test");
Integer value = (Integer) functionExecutions.getDataField(toPdxInstance(pdxData), "integerField");
assertEquals(pdxData.get("integerField"), value);
}
public static class ApplicationDomainFunctions {
private Class[] getArgumentTypes(final Object... arguments) {
Class[] argumentTypes = new Class[arguments.length];
int index = 0;
for (Object argument : arguments) {
argumentTypes[index] = arguments[index].getClass();
index++;
}
return argumentTypes;
}
@GemfireFunction
public Class[] captureConvertedArgumentTypes(final String stringValue, final Integer integerValue,
final Boolean booleanValue, final Person person, final Gender gender) {
return getArgumentTypes(stringValue, integerValue, booleanValue, person, gender);
}
@GemfireFunction
public Class[] captureUnconvertedArgumentTypes(final String stringValue, final Integer integerValue,
final Boolean booleanValue, final Object domainObject, final Object enumValue) {
return getArgumentTypes(stringValue, integerValue, booleanValue, domainObject, enumValue);
}
@GemfireFunction
public String getAddressField(final PdxInstance address, final String fieldName) {
Assert.isTrue(Address.class.getName().equals(address.getClassName()));
return String.valueOf(address.getField(fieldName));
}
@GemfireFunction
public Object getDataField(final PdxInstance data, final String fieldName) {
return data.getField(fieldName);
}
}
public static class Address {
private final String street;
private final String city;
private final String state; // refactor; use Enum!
private final String zipCode;
public Address(final String street, final String city, final String state, final String zipCode) {
Assert.hasText("The Address 'street' must be specified", street);
Assert.hasText("The Address 'city' must be specified", city);
Assert.hasText("The Address 'state' must be specified", state);
Assert.hasText("The Address 'zipCode' must be specified", zipCode);
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZipCode() {
return zipCode;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Address)) {
return false;
}
Address that = (Address) obj;
return ObjectUtils.nullSafeEquals(this.getStreet(), that.getStreet())
&& ObjectUtils.nullSafeEquals(this.getCity(), that.getCity())
&& ObjectUtils.nullSafeEquals(this.getState(), that.getState())
&& ObjectUtils.nullSafeEquals(this.getZipCode(), that.getZipCode());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getStreet());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getCity());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getState());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getZipCode());
return hashValue;
}
@Override
public String toString() {
return String.format("%1$s %2$s, %3$s %4$s", getStreet(), getCity(), getState(), getZipCode());
}
}
public static enum Gender {
FEMALE,
MALE
}
public static class Person {
private final String firstName;
private final String lastName;
public Person(final String firstName, final String lastName) {
Assert.hasText(firstName, "The person's first name must be specified!");
Assert.hasText(lastName, "The person's last name must be specified!");
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person that = (Person) obj;
return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName())
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName());
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName());
return hashValue;
}
@Override
public String toString() {
return String.format("%1$s %2$s", getFirstName(), getLastName());
}
}
public static class ComposablePdxSerializer implements PdxSerializer {
private final PdxSerializer[] pdxSerializers;
private ComposablePdxSerializer(final PdxSerializer[] pdxSerializers) {
this.pdxSerializers = pdxSerializers;
}
public static PdxSerializer compose(final PdxSerializer... pdxSerializers) {
return (pdxSerializers == null ? null : (pdxSerializers.length == 1 ? pdxSerializers[0]
: new ComposablePdxSerializer(pdxSerializers)));
}
@Override
public boolean toData(final Object obj, final PdxWriter out) {
for (PdxSerializer pdxSerializer : pdxSerializers) {
if (pdxSerializer.toData(obj, out)) {
return true;
}
}
return false;
}
@Override
public Object fromData(final Class<?> type, final PdxReader in) {
for (PdxSerializer pdxSerializer : pdxSerializers) {
Object obj = pdxSerializer.fromData(type, in);
if (obj != null) {
return obj;
}
}
return null;
}
}
public static class ComposablePdxSerializerFactoryBean implements FactoryBean<PdxSerializer>, InitializingBean {
private List<PdxSerializer> pdxSerializers = Collections.emptyList();
private PdxSerializer pdxSerializer;
public void setPdxSerializers(final List<PdxSerializer> pdxSerializers) {
this.pdxSerializers = pdxSerializers;
}
@Override
public void afterPropertiesSet() throws Exception {
pdxSerializer = ComposablePdxSerializer.compose(pdxSerializers.toArray(
new PdxSerializer[pdxSerializers.size()]));
}
@Override
public PdxSerializer getObject() throws Exception {
return pdxSerializer;
}
@Override
public Class<?> getObjectType() {
return (pdxSerializer != null ? pdxSerializer.getClass() : PdxSerializer.class);
}
@Override
public boolean isSingleton() {
return true;
}
}
public static class AddressPdxSerializer implements PdxSerializer {
@Override
public boolean toData(final Object obj, final PdxWriter out) {
if (obj instanceof Address) {
Address address = (Address) obj;
out.writeString("street", address.getStreet());
out.writeString("city", address.getCity());
out.writeString("state", address.getState());
out.writeString("zipCode", address.getZipCode());
return true;
}
return false;
}
@Override
public Object fromData(final Class<?> type, final PdxReader in) {
if (Address.class.isAssignableFrom(type)) {
return new Address(in.readString("street"), in.readString("city"), in.readString("state"),
in.readString("zipCode"));
}
return null;
}
}
public static class PersonPdxSerializer implements PdxSerializer {
@Override
public boolean toData(final Object obj, final PdxWriter out) {
if (obj instanceof Person) {
Person person = (Person) obj;
out.writeString("firstName", person.getFirstName());
out.writeString("lastName", person.getLastName());
return true;
}
return false;
}
@Override
public Object fromData(final Class<?> type, final PdxReader in) {
if (Person.class.isAssignableFrom(type)) {
return new Person(in.readString("firstName"), in.readString("lastName"));
}
return null;
}
}
}