| /* |
| * 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.assertj.core.api.Assertions.assertThat; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URLEncoder; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.http.HttpStatus; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.ClassRule; |
| import org.junit.Test; |
| import org.springframework.http.MediaType; |
| |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.cache.RegionShortcut; |
| import org.apache.geode.rest.internal.web.controllers.Customer; |
| import org.apache.geode.test.junit.rules.GeodeDevRestClient; |
| import org.apache.geode.test.junit.rules.RequiresGeodeHome; |
| import org.apache.geode.test.junit.rules.ServerStarterRule; |
| |
| public class RestRegionAPIDUnitTest { |
| @ClassRule |
| public static ServerStarterRule server = new ServerStarterRule() |
| .withRestService() |
| .withPDXReadSerialized() |
| .withRegion(RegionShortcut.REPLICATE, "regionA"); |
| |
| @ClassRule |
| public static RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome(); |
| |
| @Before |
| public void clearRegionA() { |
| getRegionA().clear(); |
| } |
| |
| private Region getRegionA() { |
| return server.getCache().getRegion("/regionA"); |
| } |
| |
| public static GeodeDevRestClient restClient; |
| |
| private static List<String> jsonDocuments = new ArrayList<>(); |
| |
| @BeforeClass |
| public static void setUpClass() throws Exception { |
| restClient = new GeodeDevRestClient("localhost", server.getHttpPort(), false); |
| initJsonDocuments(); |
| } |
| |
| @Test |
| public void getAllResources() { |
| restClient.doGetAndAssert("") |
| .hasStatusCode(HttpStatus.SC_OK) |
| .hasContentType(MediaType.APPLICATION_JSON.toString()) |
| .hasResponseBody().isEqualToIgnoringWhitespace( |
| "{\"regions\":[{\"name\":\"regionA\",\"type\":\"REPLICATE\",\"key-constraint\":null,\"value-constraint\":null}]}"); |
| } |
| |
| @Test |
| public void getRegionWhenEmpty() { |
| restClient.doGetAndAssert("/regionA") |
| .statusIsOk() |
| .hasResponseBody().isEqualToIgnoringWhitespace("{\"regionA\":[]}"); |
| } |
| |
| @Test |
| public void getRegionAWhenHasData() throws Exception { |
| Region region = getRegionA(); |
| region.put("customer1", new Customer(1L, "jon", "doe", "123-456-789")); |
| region.put("customer2", new Customer(2L, "jane", "doe", "123-456-999")); |
| |
| JsonNode jsonObject = restClient.doGetAndAssert("/regionA") |
| .statusIsOk().getJsonObject(); |
| |
| assertThat(jsonObject.get("regionA").size()).isEqualTo(2); |
| } |
| |
| |
| @Test |
| public void getSingleKey() throws Exception { |
| Region region = getRegionA(); |
| region.put("customer1", new Customer(1L, "jon", "doe", "123-456-789")); |
| |
| JsonNode jsonObject = restClient.doGetAndAssert("/regionA/customer1") |
| .statusIsOk().getJsonObject(); |
| System.out.println(jsonObject.toString()); |
| assertThat(jsonObject.get("firstName").asText()).isEqualTo("jon"); |
| } |
| |
| @Test |
| public void putIntoRegionA() { |
| for (int key = 0; key < jsonDocuments.size(); key++) { |
| restClient.doPutAndAssert("/regionA/" + key, jsonDocuments.get(key)).statusIsOk(); |
| } |
| |
| Region region = server.getCache().getRegion("regionA"); |
| assertThat(region).hasSize(jsonDocuments.size()); |
| } |
| |
| @Test |
| public void postIntoRegionA() { |
| for (int key = 0; key < jsonDocuments.size(); key++) { |
| restClient.doPostAndAssert("/regionA?key=" + key, jsonDocuments.get(key)).statusIsOk(); |
| } |
| Region region = server.getCache().getRegion("regionA"); |
| assertThat(region).hasSize(jsonDocuments.size()); |
| } |
| |
| @Test |
| public void putDuplicateWithoutReplace() { |
| Region region = getRegionA(); |
| region.put("customer1", new Customer(1L, "jon", "doe", "123-456-789")); |
| |
| // put with the same key and same value |
| restClient.doPutAndAssert("/regionA/customer1", |
| "{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .statusIsOk(); |
| |
| assertThat(region.size()).isEqualTo(1); |
| |
| // put the the same key and different value |
| restClient.doPutAndAssert("/regionA/customer1", |
| "{\"customerId\":1,\"firstName\":\"jane\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"111-111-111\"}") |
| .statusIsOk(); |
| |
| assertThat(region.size()).isEqualTo(1); |
| |
| // put the the different key |
| restClient.doPutAndAssert("/regionA/customer2", |
| "{\"customerId\":2,\"firstName\":\"jane\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"111-111-111\"}") |
| .statusIsOk(); |
| assertThat(region.size()).isEqualTo(2); |
| } |
| |
| @Test |
| public void putDuplicateWithReplace() { |
| Region region = getRegionA(); |
| region.put("customer1", new Customer(1L, "jon", "doe", "123-456-789")); |
| |
| // replace with an existing key and different value |
| restClient.doPutAndAssert("/regionA/customer1?op=REPLACE", |
| "{\"customerId\":2,\"firstName\":\"jane\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .statusIsOk(); |
| assertThat(region.size()).isEqualTo(1); |
| |
| // trying to replace with a non-existent key |
| restClient.doPutAndAssert("/regionA/customer2?op=REPLACE", |
| "{\"customerId\":2,\"firstName\":\"jane\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .hasStatusCode(HttpStatus.SC_NOT_FOUND); |
| assertThat(region.size()).isEqualTo(1); |
| } |
| |
| @Test |
| public void putWithCAS() { |
| Region region = server.getCache().getRegion("/regionA"); |
| // do a regular put first |
| restClient.doPutAndAssert("/regionA/customer1", |
| "{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\"}") |
| .statusIsOk(); |
| assertThat(region).hasSize(1); |
| |
| // invalid json doc in the request |
| restClient.doPutAndAssert("/regionA/customer1?op=CAS", |
| "{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\"}") |
| .hasStatusCode(HttpStatus.SC_BAD_REQUEST); |
| |
| // put with CAS with existing key, but with different old value |
| restClient.doPutAndAssert("/regionA/customer1?op=CAS", |
| "{\"@old\":{\"customerId\":1,\"firstName\":\"jane\",\"lastName\":\"doe\"}," |
| + "\"@new\":{\"customerId\":1,\"firstName\":\"mary\",\"lastName\":\"doe\"}}") |
| .hasStatusCode(HttpStatus.SC_CONFLICT) |
| .hasResponseBody() |
| .isEqualToIgnoringWhitespace( |
| "{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\"}"); |
| |
| // put with CAS with different key, status is ok, but did not put a second entry in |
| restClient.doPutAndAssert("/regionA/customer2?op=CAS", |
| "{\"@old\":{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\"}," |
| + "\"@new\":{\"customerId\":1,\"firstName\":\"mary\",\"lastName\":\"doe\"}}") |
| .hasStatusCode(HttpStatus.SC_OK); |
| assertThat(region).hasSize(1); |
| |
| // put with CAS successfully (with existing key, with correct old value) |
| restClient.doPutAndAssert("/regionA/customer1?op=CAS", |
| "{\"@old\":{\"customerId\":1,\"firstName\":\"jon\",\"lastName\":\"doe\"}," |
| + "\"@new\":{\"customerId\":1,\"firstName\":\"jane\",\"lastName\":\"doe\"}}") |
| .hasStatusCode(HttpStatus.SC_OK); |
| |
| assertThat(region).hasSize(1); |
| } |
| |
| @Test |
| public void deleteAll() { |
| Region region = getRegionA(); |
| region.put("1", new Customer(1L, "jon", "doe", "123-456-789")); |
| region.put("2", new Customer(2L, "jane", "doe", "123-456-999")); |
| region.put("3", new Customer(3L, "mary", "doe", "123-456-899")); |
| |
| restClient.doDeleteAndAssert("/regionA").statusIsOk(); |
| assertThat(region.size()).isEqualTo(0); |
| } |
| |
| @Test |
| public void deleteWithKeys() { |
| Region region = getRegionA(); |
| region.put("1", new Customer(1L, "jon", "doe", "123-456-789")); |
| region.put("2", new Customer(2L, "jane", "doe", "123-456-999")); |
| region.put("3", new Customer(3L, "mary", "doe", "123-456-899")); |
| |
| restClient.doDeleteAndAssert("/regionA/1,2").statusIsOk(); |
| assertThat(region.size()).isEqualTo(1); |
| assertThat(region.get("3")).isNotNull(); |
| } |
| |
| @Test |
| public void deleteNonExistentKey() { |
| restClient.doDeleteAndAssert("/regionA/1234").hasStatusCode(HttpStatus.SC_NOT_FOUND); |
| } |
| |
| @Test |
| public void listKeys() throws Exception { |
| Region region = getRegionA(); |
| region.put("customer1", new Customer(1L, "jon", "doe", "123-456-789")); |
| region.put("customer2", new Customer(2L, "jane", "doe", "123-456-999")); |
| |
| JsonNode jsonObject = |
| restClient.doGetAndAssert("/regionA/keys").statusIsOk().getJsonObject(); |
| assertThat(jsonObject.get("keys").toString()).isEqualTo("[\"customer1\",\"customer2\"]"); |
| } |
| |
| @Test |
| public void adhocQuery() throws Exception { |
| restClient.doPutAndAssert("/regionA/1", DOCUMENT).statusIsOk(); |
| restClient.doPutAndAssert("/regionA/2", DOCUMENT1).statusIsOk(); |
| restClient.doPutAndAssert("/regionA/3", DOCUMENT2).statusIsOk(); |
| |
| String urlPrefix = |
| "/queries/adhoc?q=" + URLEncoder.encode( |
| "SELECT book.displayprice FROM /regionA e, e.store.book book WHERE book.displayprice > 5", |
| "UTF-8"); |
| |
| restClient.doGetAndAssert(urlPrefix).statusIsOk().hasJsonArrayOfDoubles().hasSize(12) |
| .containsExactlyInAnyOrder(18.95, 18.95, 8.99, 8.99, 8.99, 8.95, 22.99, 22.99, 22.99, 12.99, |
| 112.99, 112.99); |
| } |
| |
| @Test |
| public void preparedQuery() throws Exception { |
| restClient.doPutAndAssert("/regionA/1", DOCUMENT).statusIsOk(); |
| restClient.doPutAndAssert("/regionA/2", DOCUMENT1).statusIsOk(); |
| restClient.doPutAndAssert("/regionA/3", DOCUMENT2).statusIsOk(); |
| |
| // create 5 prepared statements |
| for (int i = 0; i < 5; i++) { |
| String urlPrefix = "/queries/?id=" + "Query" + i + "&q=" + URLEncoder.encode( |
| "SELECT book.displayprice FROM /regionA e, e.store.book book WHERE book.displayprice > $1", |
| "UTF-8"); |
| restClient.doPostAndAssert(urlPrefix, "").statusIsOk(); |
| } |
| |
| // get the list of defined queries and verify the size of them |
| JsonNode jsonObject = restClient.doGetAndAssert("/queries").statusIsOk().getJsonObject(); |
| assertThat(jsonObject.get("queries").size()).isEqualTo(5); |
| |
| // execute each defined queries |
| for (int i = 0; i < 5; i++) { |
| restClient.doPostAndAssert("/queries/Query" + i, "[{\"@type\":\"double\",\"@value\":8.99}]") |
| .statusIsOk() |
| .hasJsonArrayOfDoubles().hasSize(8) |
| .containsExactlyInAnyOrder(18.95, 18.95, 22.99, 22.99, 22.99, 12.99, 112.99, 112.99); |
| } |
| } |
| |
| @Test |
| public void concurrentRequests() throws ExecutionException, InterruptedException { |
| ExecutorService pool = Executors.newFixedThreadPool(5); |
| List<Future<Boolean>> futures = new ArrayList<>(); |
| |
| int entryCount = 20; |
| futures.add(pool.submit(() -> { |
| for (int i = 0; i < entryCount; i++) { |
| restClient.doPostAndAssert("/regionA?key=customer" + i, |
| "{\"customerId\":" + i |
| + ",\"firstName\":\"jon\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .hasStatusCode(HttpStatus.SC_CREATED, HttpStatus.SC_CONFLICT); |
| } |
| return true; |
| })); |
| |
| futures.add(pool.submit(() -> { |
| for (int i = 0; i < entryCount; i++) { |
| restClient.doPutAndAssert("/regionA/customer" + i, |
| "{\"customerId\":" + i |
| + ",\"firstName\":\"jon\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .hasStatusCode(HttpStatus.SC_OK) |
| .hasResponseBody().isEmpty(); |
| } |
| return true; |
| })); |
| |
| futures.add(pool.submit(() -> { |
| for (int i = 0; i < entryCount; i++) { |
| restClient.doPutAndAssert("/regionA/customer" + i + "?op=REPLACE", |
| "{\"customerId\":" + i |
| + ",\"firstName\":\"jon\",\"lastName\":\"doe\",\"socialSecurityNumber\":\"123-456-789\"}") |
| .hasStatusCode(HttpStatus.SC_OK, HttpStatus.SC_NOT_FOUND); |
| } |
| return true; |
| })); |
| |
| futures.add(pool.submit(() -> { |
| for (int i = 0; i < entryCount; i++) { |
| restClient.doGetAndAssert("/regionA/customer" + i).hasStatusCode(HttpStatus.SC_OK, |
| HttpStatus.SC_NOT_FOUND); |
| } |
| return true; |
| })); |
| |
| futures.add(pool.submit(() -> { |
| for (int i = 0; i < entryCount; i++) { |
| restClient.doDeleteAndAssert("/regionA/customer" + i).hasStatusCode(HttpStatus.SC_OK, |
| HttpStatus.SC_NOT_FOUND); |
| } |
| return true; |
| })); |
| |
| for (Future<Boolean> future : futures) { |
| future.get(); |
| } |
| } |
| |
| private static void initJsonDocuments() throws URISyntaxException, IOException { |
| ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
| URI uri = classLoader.getResource("sampleJson.json").toURI(); |
| |
| String rawJson = IOUtils.toString(uri, Charset.defaultCharset()); |
| |
| ObjectMapper mapper = new ObjectMapper(); |
| JsonNode json = mapper.readTree(rawJson); |
| |
| for (int i = 0; i < json.size(); i++) { |
| jsonDocuments.add(json.get(i).toString()); |
| } |
| } |
| |
| private static final String DOCUMENT = "{ \"store\": {\n" + " \"book\": [ \n" |
| + " { \"category\": \"reference\",\n" + " \"author\": \"Nigel Rees\",\n" |
| + " \"title\": \"Sayings of the Century\",\n" + " \"displayprice\": 8.95\n" |
| + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Evelyn Waugh\",\n" + " \"title\": \"Sword of Honour\",\n" |
| + " \"displayprice\": 12.99\n" + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Herman Melville\",\n" + " \"title\": \"Moby Dick\",\n" |
| + " \"isbn\": \"0-553-21311-3\",\n" + " \"displayprice\": 8.99\n" + " },\n" |
| + " { \"category\": \"fiction\",\n" + " \"author\": \"J. R. R. Tolkien\",\n" |
| + " \"title\": \"The Lord of the Rings\",\n" + " \"isbn\": \"0-395-19395-8\",\n" |
| + " \"displayprice\": 22.99\n" + " }\n" + " ],\n" + " \"bicycle\": {\n" |
| + " \"color\": \"red\",\n" + " \"displayprice\": 19.95,\n" |
| + " \"foo:bar\": \"fooBar\",\n" + " \"dot.notation\": \"new\",\n" |
| + " \"dash-notation\": \"dashes\"\n" + " }\n" + " }\n" + "}"; |
| |
| private static final String DOCUMENT1 = "{ \"store\": {\n" + " \"book\": [ \n" |
| + " { \"category\": \"reference\",\n" + " \"author\": \"Nigel Rees\",\n" |
| + " \"title\": \"Sayings of the Century\",\n" + " \"displayprice\": 18.95\n" |
| + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Evelyn Waugh\",\n" + " \"title\": \"Sword of Honour\",\n" |
| + " \"displayprice\": 112.99\n" + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Herman Melville\",\n" + " \"title\": \"Moby Dick\",\n" |
| + " \"isbn\": \"0-553-21311-3\",\n" + " \"displayprice\": 8.99\n" + " },\n" |
| + " { \"category\": \"fiction\",\n" + " \"author\": \"J. R. R. Tolkien\",\n" |
| + " \"title\": \"The Lord of the Rings\",\n" + " \"isbn\": \"0-395-19395-8\",\n" |
| + " \"displayprice\": 22.99\n" + " }\n" + " ],\n" + " \"bicycle\": {\n" |
| + " \"color\": \"red\",\n" + " \"displayprice\": 19.95,\n" |
| + " \"foo:bar\": \"fooBar\",\n" + " \"dot.notation\": \"new\",\n" |
| + " \"dash-notation\": \"dashes\"\n" + " }\n" + " }\n" + "}"; |
| |
| private static final String DOCUMENT2 = "{ \"store\": {\n" + " \"book\": [ \n" |
| + " { \"category\": \"reference\",\n" + " \"author\": \"Nigel Rees\",\n" |
| + " \"title\": \"Sayings of the Century\",\n" + " \"displayprice\": 18.95\n" |
| + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Evelyn Waugh\",\n" + " \"title\": \"Sword of Honour\",\n" |
| + " \"displayprice\": 112.99\n" + " },\n" + " { \"category\": \"fiction\",\n" |
| + " \"author\": \"Herman Melville\",\n" + " \"title\": \"Moby Dick\",\n" |
| + " \"isbn\": \"0-553-21311-3\",\n" + " \"displayprice\": 8.99\n" + " },\n" |
| + " { \"category\": \"fiction\",\n" + " \"author\": \"J. R. R. Tolkien\",\n" |
| + " \"title\": \"The Lord of the Rings\",\n" + " \"isbn\": \"0-395-19395-8\",\n" |
| + " \"displayprice\": 22.99\n" + " }\n" + " ],\n" + " \"bicycle\": {\n" |
| + " \"color\": \"red\",\n" + " \"displayprice\": 129.95,\n" |
| + " \"foo:bar\": \"fooBar\",\n" + " \"dot.notation\": \"new\",\n" |
| + " \"dash-notation\": \"dashes\"\n" + " }\n" + " }\n" + "}"; |
| } |