| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You 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.apache.geode.rest.internal.web; |
| |
| import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_BIND_ADDRESS; |
| import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT; |
| import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL; |
| import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; |
| import static org.apache.geode.distributed.ConfigurationProperties.START_DEV_REST_API; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNotSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import javax.annotation.Resource; |
| |
| import com.fasterxml.jackson.core.JsonParser.Feature; |
| import com.fasterxml.jackson.databind.DeserializationFeature; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.http.HttpStatus; |
| import org.springframework.http.client.ClientHttpResponse; |
| import org.springframework.http.converter.HttpMessageConverter; |
| import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean; |
| import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
| import org.springframework.util.Assert; |
| import org.springframework.util.ObjectUtils; |
| import org.springframework.util.StringUtils; |
| import org.springframework.web.client.ResponseErrorHandler; |
| import org.springframework.web.client.RestTemplate; |
| |
| import org.apache.geode.cache.Cache; |
| import org.apache.geode.cache.CacheFactory; |
| import org.apache.geode.cache.DataPolicy; |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.cache.RegionFactory; |
| import org.apache.geode.cache.RegionService; |
| import org.apache.geode.internal.AvailablePortHelper; |
| import org.apache.geode.internal.GemFireVersion; |
| import org.apache.geode.internal.util.IOUtils; |
| import org.apache.geode.management.internal.AgentUtil; |
| import org.apache.geode.pdx.PdxInstance; |
| import org.apache.geode.pdx.PdxReader; |
| import org.apache.geode.pdx.PdxSerializable; |
| import org.apache.geode.pdx.PdxWriter; |
| import org.apache.geode.pdx.ReflectionBasedAutoSerializer; |
| import org.apache.geode.test.junit.categories.RestAPITest; |
| import org.apache.geode.test.junit.rules.RequiresGeodeHome; |
| |
| /** |
| * The GemFireRestInterfaceTest class is a test suite of test cases testing the contract and |
| * functionality of the GemFire Developer REST API, mixing Java clients, this test GemFire's Cache |
| * Region API, along with a REST-based client, also this test using Spring's RestTemplate, testing |
| * the proper interaction, especially in the case of an application domain object type having a |
| * java.util.Date property. |
| * |
| * @see org.junit.Test |
| * @see org.junit.runner.RunWith |
| * @see org.springframework.web.client.RestTemplate |
| * @see org.apache.geode.cache.Cache |
| * @see org.apache.geode.cache.Region |
| * @see org.apache.geode.pdx.PdxInstance |
| * @see org.apache.geode.pdx.ReflectionBasedAutoSerializer |
| * @since Geode 1.0.0 |
| */ |
| @Category({RestAPITest.class}) |
| public class RestInterfaceIntegrationTest { |
| |
| @Rule |
| public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome(); |
| |
| protected static int DEFAULT_HTTP_SERVICE_PORT = 8189; |
| |
| protected static final String REST_API_SERVICE_ENDPOINT = |
| "http://localhost:%1$d/gemfire-api/v1/%2$s/%3$s"; |
| protected static final String UTF_8 = "UTF-8"; |
| |
| @Autowired |
| private Cache gemfireCache; |
| |
| @Autowired |
| private ObjectMapper objectMapper; |
| |
| @Resource(name = "gemfireProperties") |
| private Properties gemfireProperties; |
| |
| @Resource(name = "People") |
| private Region<String, Object> people; |
| |
| @Before |
| public void setupGemFire() { |
| AgentUtil agentUtil = new AgentUtil(GemFireVersion.getGemFireVersion()); |
| if (agentUtil.findWarLocation("geode-web-api") == null) { |
| fail("unable to locate geode-web-api WAR file"); |
| } |
| |
| if (gemfireCache == null) { |
| gemfireProperties = (gemfireProperties != null ? gemfireProperties : new Properties()); |
| |
| gemfireCache = new CacheFactory() |
| // .setPdxSerializer(new |
| // ReflectionBasedAutoSerializer(Person.class.getPackage().getName().concat(".*"))) |
| .setPdxSerializer( |
| new ReflectionBasedAutoSerializer(Person.class.getName().replaceAll("\\$", "."))) |
| .setPdxReadSerialized(true).setPdxIgnoreUnreadFields(false) |
| .set("name", getClass().getSimpleName()).set(MCAST_PORT, "0").set(LOG_LEVEL, "config") |
| .set(HTTP_SERVICE_BIND_ADDRESS, "localhost") |
| .set(HTTP_SERVICE_PORT, String.valueOf(getHttpServicePort())) |
| .set(START_DEV_REST_API, "true").create(); |
| |
| RegionFactory<String, Object> peopleRegionFactory = gemfireCache.createRegionFactory(); |
| |
| peopleRegionFactory.setDataPolicy(DataPolicy.PARTITION); |
| peopleRegionFactory.setKeyConstraint(String.class); |
| peopleRegionFactory.setValueConstraint(Object.class); |
| |
| people = peopleRegionFactory.create("People"); |
| } |
| } |
| |
| @After |
| public void tearDown() { |
| gemfireCache.close(); |
| } |
| |
| protected synchronized int getHttpServicePort() { |
| try { |
| return Integer |
| .parseInt(StringUtils.trimWhitespace(gemfireProperties.getProperty(HTTP_SERVICE_PORT))); |
| } catch (NumberFormatException ignore) { |
| int httpServicePort = getHttpServicePort(DEFAULT_HTTP_SERVICE_PORT); |
| gemfireProperties.setProperty(HTTP_SERVICE_PORT, String.valueOf(httpServicePort)); |
| return httpServicePort; |
| } |
| } |
| |
| private int getHttpServicePort(final int defaultHttpServicePort) { |
| int httpServicePort = AvailablePortHelper.getRandomAvailableTCPPort(); |
| return (httpServicePort > 1024 && httpServicePort < 65536 ? httpServicePort |
| : defaultHttpServicePort); |
| } |
| |
| protected ObjectMapper getObjectMapper() { |
| if (objectMapper == null) { |
| Jackson2ObjectMapperFactoryBean objectMapperFactoryBean = |
| new Jackson2ObjectMapperFactoryBean(); |
| |
| objectMapperFactoryBean.setFailOnEmptyBeans(true); |
| objectMapperFactoryBean.setFeaturesToEnable(Feature.ALLOW_COMMENTS); |
| objectMapperFactoryBean.setFeaturesToEnable(Feature.ALLOW_SINGLE_QUOTES); |
| objectMapperFactoryBean |
| .setFeaturesToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); |
| objectMapperFactoryBean |
| .setFeaturesToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); |
| objectMapperFactoryBean.setIndentOutput(true); |
| objectMapperFactoryBean.setSimpleDateFormat("MM/dd/yyyy"); |
| objectMapperFactoryBean.afterPropertiesSet(); |
| |
| objectMapper = objectMapperFactoryBean.getObject(); |
| } |
| |
| return objectMapper; |
| } |
| |
| protected Region<String, Object> getPeopleRegion() { |
| assertNotNull("The 'People' Region was not properly initialized!", people); |
| return people; |
| } |
| |
| protected String getAdhocQueryRestApiEndpoint(final String query) { |
| return getAdhocQueryRestApiEndpoint(getHttpServicePort(), query); |
| } |
| |
| protected String getAdhocQueryRestApiEndpoint(final int httpServicePort, final String query) { |
| return String.format(REST_API_SERVICE_ENDPOINT, httpServicePort, "queries", |
| String.format("adhoc?q=%1$s", query)); |
| } |
| |
| protected String getRegionGetRestApiEndpoint(final Region<?, ?> region, final String key) { |
| return getRegionGetRestApiEndpoint(getHttpServicePort(), region, key); |
| } |
| |
| protected String getRegionGetRestApiEndpoint(final int httpServicePort, final Region<?, ?> region, |
| final String key) { |
| return String.format(REST_API_SERVICE_ENDPOINT, httpServicePort, region.getName(), key); |
| } |
| |
| protected Date createDate(final int year, final int month, final int dayOfMonth) { |
| Calendar dateTime = Calendar.getInstance(); |
| dateTime.clear(); |
| dateTime.set(Calendar.YEAR, year); |
| dateTime.set(Calendar.MONTH, month); |
| dateTime.set(Calendar.DAY_OF_MONTH, dayOfMonth); |
| return dateTime.getTime(); |
| } |
| |
| protected Person createPerson(final String firstName, final String lastName) { |
| return createPerson(firstName, lastName, null); |
| } |
| |
| protected Person createPerson(final String firstName, final String lastName, |
| final Date birthDate) { |
| return new Person(firstName, lastName, birthDate); |
| } |
| |
| protected RestTemplate createRestTemplate() { |
| MappingJackson2HttpMessageConverter httpMessageConverter = |
| new MappingJackson2HttpMessageConverter(); |
| |
| httpMessageConverter.setObjectMapper(getObjectMapper()); |
| |
| return setErrorHandler( |
| new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(httpMessageConverter))); |
| } |
| |
| private RestTemplate setErrorHandler(final RestTemplate restTemplate) { |
| restTemplate.setErrorHandler(new ResponseErrorHandler() { |
| private final Set<HttpStatus> errorStatuses = new HashSet<>(); |
| |
| /* non-static */ { |
| errorStatuses.add(HttpStatus.BAD_REQUEST); |
| errorStatuses.add(HttpStatus.UNAUTHORIZED); |
| errorStatuses.add(HttpStatus.FORBIDDEN); |
| errorStatuses.add(HttpStatus.NOT_FOUND); |
| errorStatuses.add(HttpStatus.METHOD_NOT_ALLOWED); |
| errorStatuses.add(HttpStatus.NOT_ACCEPTABLE); |
| errorStatuses.add(HttpStatus.REQUEST_TIMEOUT); |
| errorStatuses.add(HttpStatus.CONFLICT); |
| errorStatuses.add(HttpStatus.REQUEST_ENTITY_TOO_LARGE); |
| errorStatuses.add(HttpStatus.REQUEST_URI_TOO_LONG); |
| errorStatuses.add(HttpStatus.UNSUPPORTED_MEDIA_TYPE); |
| errorStatuses.add(HttpStatus.TOO_MANY_REQUESTS); |
| errorStatuses.add(HttpStatus.INTERNAL_SERVER_ERROR); |
| errorStatuses.add(HttpStatus.NOT_IMPLEMENTED); |
| errorStatuses.add(HttpStatus.BAD_GATEWAY); |
| errorStatuses.add(HttpStatus.SERVICE_UNAVAILABLE); |
| } |
| |
| @Override |
| public boolean hasError(final ClientHttpResponse response) throws IOException { |
| return errorStatuses.contains(response.getStatusCode()); |
| } |
| |
| @Override |
| public void handleError(final ClientHttpResponse response) throws IOException { |
| System.err.printf("%1$d - %2$s%n", response.getRawStatusCode(), response.getStatusText()); |
| System.err.println(readBody(response)); |
| } |
| |
| private String readBody(final ClientHttpResponse response) throws IOException { |
| BufferedReader responseBodyReader = null; |
| |
| try { |
| responseBodyReader = new BufferedReader(new InputStreamReader(response.getBody())); |
| |
| StringBuilder buffer = new StringBuilder(); |
| String line; |
| |
| while ((line = responseBodyReader.readLine()) != null) { |
| buffer.append(line).append(System.getProperty("line.separator")); |
| } |
| |
| return buffer.toString().trim(); |
| } finally { |
| IOUtils.close(responseBodyReader); |
| } |
| } |
| }); |
| |
| return restTemplate; |
| } |
| |
| @Test |
| public void testRegionObjectWithDatePropertyAccessedWithRestApi() throws Exception { |
| String key = "1"; |
| Person jonDoe = createPerson("Jon", "Doe", createDate(1977, Calendar.OCTOBER, 31)); |
| |
| assertTrue(getPeopleRegion().isEmpty()); |
| |
| getPeopleRegion().put(key, jonDoe); |
| |
| assertFalse(getPeopleRegion().isEmpty()); |
| assertEquals(1, getPeopleRegion().size()); |
| assertTrue(getPeopleRegion().containsKey(key)); |
| |
| Object jonDoeRef = getPeopleRegion().get(key); |
| |
| assertTrue(jonDoeRef instanceof PdxInstance); |
| assertEquals(jonDoe.getClass().getName(), ((PdxInstance) jonDoeRef).getClassName()); |
| assertEquals(jonDoe.getFirstName(), ((PdxInstance) jonDoeRef).getField("firstName")); |
| assertEquals(jonDoe.getLastName(), ((PdxInstance) jonDoeRef).getField("lastName")); |
| assertEquals(jonDoe.getBirthDate(), ((PdxInstance) jonDoeRef).getField("birthDate")); |
| |
| RestTemplate restTemplate = createRestTemplate(); |
| |
| Person jonDoeResource = restTemplate |
| .getForObject(getRegionGetRestApiEndpoint(getPeopleRegion(), key), Person.class); |
| |
| assertNotNull(jonDoeResource); |
| assertNotSame(jonDoe, jonDoeResource); |
| assertEquals(jonDoe, jonDoeResource); |
| |
| /* |
| * Object result = runQueryUsingApi(getPeopleRegion().getRegionService(), |
| * String.format("SELECT * FROM %1$s", getPeopleRegion().getFullPath())); |
| * |
| * System.out.printf("(OQL Query using API) Person is (%1$s)%n", result); |
| */ |
| |
| String url = getAdhocQueryRestApiEndpoint( |
| String.format("SELECT * FROM %1$s", getPeopleRegion().getFullPath())); |
| |
| System.out.printf("URL (%1$s)%n", url); |
| |
| List<?> queryResults = restTemplate.getForObject(url, List.class); |
| |
| assertNotNull(queryResults); |
| assertFalse(queryResults.isEmpty()); |
| assertEquals(1, queryResults.size()); |
| |
| jonDoeResource = objectMapper.convertValue(queryResults.get(0), Person.class); |
| |
| assertNotNull(jonDoeResource); |
| assertNotSame(jonDoe, jonDoeResource); |
| assertEquals(jonDoe, jonDoeResource); |
| } |
| |
| private Object runQueryUsingApi(final RegionService regionService, final String queryString) |
| throws Exception { |
| return regionService.getQueryService().newQuery(queryString).execute(); |
| } |
| |
| // @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE) |
| // @JsonIgnoreProperties(ignoreUnknown = true) |
| // @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = |
| // "@type") |
| public static class Person implements PdxSerializable { |
| |
| protected static final String DEFAULT_BIRTH_DATE_FORMAT_PATTERN = "MM/dd/yyyy"; |
| |
| private Date birthDate; |
| |
| private String firstName; |
| private String lastName; |
| |
| public Person() {} |
| |
| public Person(final String firstName, final String lastName) { |
| this(firstName, lastName, null); |
| } |
| |
| public Person(final String firstName, final String lastName, final Date birthDate) { |
| setFirstName(firstName); |
| setLastName(lastName); |
| setBirthDate(birthDate); |
| } |
| |
| public Date getBirthDate() { |
| return birthDate; |
| } |
| |
| public void setBirthDate(final Date birthDate) { |
| Assert.isTrue(birthDate == null || birthDate.compareTo(Calendar.getInstance().getTime()) <= 0, |
| "A Person's date of birth cannot be after today!"); |
| this.birthDate = birthDate; |
| } |
| |
| public String getFirstName() { |
| return firstName; |
| } |
| |
| public void setFirstName(final String firstName) { |
| Assert.hasText(firstName, "The Person must have a first name!"); |
| this.firstName = firstName; |
| } |
| |
| public String getLastName() { |
| return lastName; |
| } |
| |
| public void setLastName(final String lastName) { |
| Assert.hasText(firstName, "The Person must have a last name!"); |
| this.lastName = lastName; |
| } |
| |
| protected String format(final Date dateTime) { |
| return format(dateTime, DEFAULT_BIRTH_DATE_FORMAT_PATTERN); |
| } |
| |
| protected String format(final Date dateTime, final String dateFormatPattern) { |
| return (dateTime == null ? null |
| : new SimpleDateFormat(StringUtils.hasText(dateFormatPattern) ? dateFormatPattern |
| : DEFAULT_BIRTH_DATE_FORMAT_PATTERN).format(dateTime)); |
| } |
| |
| @Override |
| public void toData(final PdxWriter writer) { |
| writer.writeString("firstName", getFirstName()); |
| writer.writeString("lastName", getLastName()); |
| writer.writeDate("birthDate", getBirthDate()); |
| } |
| |
| @Override |
| public void fromData(final PdxReader reader) { |
| setFirstName(reader.readString("firstName")); |
| setLastName(reader.readString("lastName")); |
| setBirthDate(reader.readDate("birthDate")); |
| } |
| |
| @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()) |
| && ObjectUtils.nullSafeEquals(this.getBirthDate(), that.getBirthDate()); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashValue = 17; |
| hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName()); |
| hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName()); |
| hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getBirthDate()); |
| return hashValue; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("{ @type = %1$s, firstName = %2$s, lastName = %3$s, birthDate = %4$s }", |
| getClass().getName(), getFirstName(), getLastName(), format(getBirthDate())); |
| } |
| } |
| |
| } |