blob: 3840a50ac869691cfb6be4090e0b7a3bb845c193 [file] [log] [blame]
/*
* 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" + "}";
}