/*
 * 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.controllers;

import static org.apache.geode.cache.Region.SEPARATOR;
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.JMX_MANAGER;
import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
import static org.apache.geode.distributed.ConfigurationProperties.START_DEV_REST_API;
import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

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.RegionShortcut;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientCacheFactory;
import org.apache.geode.cache.client.ClientRegionFactory;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.xmlcache.RegionAttributesCreation;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.test.dunit.NetworkUtils;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.junit.categories.RestAPITest;
import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;

/**
 * Dunit Test containing inter - operations between REST Client and Gemfire cache client
 *
 * Apache HttpComponents 5.x migration notes:
 * - ClassicHttpResponse replaces CloseableHttpResponse for synchronous HTTP exchanges
 * - response.getCode() replaces response.getStatusLine().getStatusCode()
 * HttpComponents 5.x simplified the API by providing direct status code access
 *
 * @since GemFire 8.0
 */
@SuppressWarnings("deprecation")
@Category({RestAPITest.class})
@RunWith(Parameterized.class)
@Parameterized.UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
public class RestAPIsAndInterOpsDUnitTest
    extends org.apache.geode.cache.client.internal.LocatorTestBase {

  private static final String PEOPLE_REGION_NAME = "People";

  private static final String findAllPeopleQuery =
      "/queries?id=findAllPeople&q=SELECT%20*%20FROM%20" + SEPARATOR + "People";
  private static final String findPeopleByGenderQuery =
      "/queries?id=filterByGender&q=SELECT%20*%20from%20" + SEPARATOR
          + "People%20where%20gender=$1";
  private static final String findPeopleByLastNameQuery =
      "/queries?id=filterByLastName&q=SELECT%20*%20from%20" + SEPARATOR
          + "People%20where%20lastName=$1";

  private static final String[] PARAM_QUERY_IDS_ARRAY =
      {"findAllPeople", "filterByGender", "filterByLastName"};

  private static final String QUERY_ARGS =
      "[" + "{" + "\"@type\": \"string\"," + "\"@value\": \"Patel\"" + "}" + "]";

  private static final String PERSON_AS_JSON_REPLACE =
      "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\"," + "\"id\": 501,"
          + " \"firstName\": \"Barack\"," + " \"middleName\": \"Hussein\","
          + " \"lastName\": \"Obama\"," + " \"birthDate\": \"04/08/1961\"," + "\"gender\": \"MALE\""
          + "}";

  private static final String PERSON_LIST_AS_JSON = "[" + "{"
      + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\"," + "\"id\": 3,"
      + " \"firstName\": \"Nishka3\"," + " \"middleName\": \"Nilkanth3\","
      + " \"lastName\": \"Patel3\"," + " \"birthDate\": \"07/31/2009\"," + "\"gender\": \"FEMALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 4," + " \"firstName\": \"Tanay4\"," + " \"middleName\": \"kiran4\","
      + " \"lastName\": \"Patel4\"," + " \"birthDate\": \"23/08/2012\"," + "\"gender\": \"MALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 5," + " \"firstName\": \"Nishka5\"," + " \"middleName\": \"Nilkanth5\","
      + " \"lastName\": \"Patel5\"," + " \"birthDate\": \"31/09/2009\"," + "\"gender\": \"FEMALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 6," + " \"firstName\": \"Tanay6\"," + " \"middleName\": \"Kiran6\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"23/08/2012\"," + "\"gender\": \"MALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 7," + " \"firstName\": \"Nishka7\"," + " \"middleName\": \"Nilkanth7\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"31/09/2009\"," + "\"gender\": \"FEMALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 8," + " \"firstName\": \"Tanay8\"," + " \"middleName\": \"kiran8\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"23/08/2012\"," + "\"gender\": \"MALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 9," + " \"firstName\": \"Nishka9\"," + " \"middleName\": \"Nilkanth9\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"31/09/2009\"," + "\"gender\": \"FEMALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 10," + " \"firstName\": \"Tanay10\"," + " \"middleName\": \"kiran10\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"23/08/2012\"," + "\"gender\": \"MALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 11," + " \"firstName\": \"Nishka11\"," + " \"middleName\": \"Nilkanth11\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"31/09/2009\"," + "\"gender\": \"FEMALE\""
      + "}," + "{" + "\"@type\": \"org.apache.geode.rest.internal.web.controllers.Person\","
      + "\"id\": 12," + " \"firstName\": \"Tanay12\"," + " \"middleName\": \"kiran12\","
      + " \"lastName\": \"Patel\"," + " \"birthDate\": \"23/08/2012\"," + "\"gender\": \"MALE\""
      + "}" + "]";

  @Parameterized.Parameter
  public String urlContext;

  @Parameterized.Parameters
  public static Collection<String> data() {
    return Arrays.asList("/geode", "/gemfire-api");
  }

  @SuppressWarnings("unchecked")
  private CacheServer createRegionAndStartCacheServer(String[] regions, Cache cache)
      throws IOException {
    RegionAttributesCreation regionAttributes = new RegionAttributesCreation();
    regionAttributes.setEnableBridgeConflation(true);
    regionAttributes.setDataPolicy(DataPolicy.REPLICATE);

    for (String region : regions) {
      cache.createRegionFactory(regionAttributes).create(region);
    }

    CacheServer server = cache.addCacheServer();
    server.setPort(0);
    server.setLoadProbe(CacheServer.DEFAULT_LOAD_PROBE);
    server.start();

    return server;
  }

  private int startManager(final String locators, final String[] regions) throws IOException {
    int httpPort = getRandomAvailableTCPPort();

    Properties props = new Properties();
    props.setProperty(MCAST_PORT, String.valueOf(0));
    props.setProperty(LOCATORS, locators);

    props.setProperty(JMX_MANAGER, "true");
    props.setProperty(JMX_MANAGER_START, "true");
    props.setProperty(JMX_MANAGER_PORT, "0");

    // Set REST service related configuration
    props.setProperty(START_DEV_REST_API, "true");
    props.setProperty(HTTP_SERVICE_BIND_ADDRESS, "localhost");
    props.setProperty(HTTP_SERVICE_PORT, String.valueOf(httpPort));

    Cache cache = new CacheFactory(props).create();
    CacheServer server = createRegionAndStartCacheServer(regions, cache);

    return server.getPort();
  }

  private String startBridgeServerWithRestService(final String hostName, final String locators,
      final String[] regions) throws IOException {
    final int serverPort = getRandomAvailableTCPPort();
    // create Cache of given VM and start HTTP service with REST APIs service
    Properties props = new Properties();
    props.setProperty(MCAST_PORT, String.valueOf(0));
    props.setProperty(LOCATORS, locators);
    props.setProperty(START_DEV_REST_API, "true");
    props.setProperty(HTTP_SERVICE_BIND_ADDRESS, hostName);
    props.setProperty(HTTP_SERVICE_PORT, String.valueOf(serverPort));

    InternalCache cache =
        (InternalCache) new CacheFactory(props).setPdxReadSerialized(true).create();
    CacheServer server = createRegionAndStartCacheServer(regions, cache);

    remoteObjects.put(CACHE_KEY, cache);
    server.getPort();

    return "http://" + hostName + ":" + serverPort + urlContext + "/v1";
  }

  private void doPutsInClientCache() {
    ClientCache cache = ClientCacheFactory.getAnyInstance();
    assertThat(cache).isNotNull();
    Region<String, Object> region = cache.getRegion(PEOPLE_REGION_NAME);

    // put person object
    final Person person1 = new Person(101L, "Mithali", "Dorai", "Raj",
        DateTimeUtils.createDate(1982, Calendar.DECEMBER, 4), Gender.FEMALE);
    final Person person2 = new Person(102L, "Sachin", "Ramesh", "Tendulkar",
        DateTimeUtils.createDate(1975, Calendar.DECEMBER, 14), Gender.MALE);
    final Person person3 = new Person(103L, "Saurabh", "Baburav", "Ganguly",
        DateTimeUtils.createDate(1972, Calendar.AUGUST, 29), Gender.MALE);
    final Person person4 = new Person(104L, "Rahul", "subrymanyam", "Dravid",
        DateTimeUtils.createDate(1979, Calendar.MARCH, 17), Gender.MALE);
    final Person person5 = new Person(105L, "Jhulan", "Chidambaram", "Goswami",
        DateTimeUtils.createDate(1983, Calendar.NOVEMBER, 25), Gender.FEMALE);

    region.put("1", person1);
    region.put("2", person2);
    region.put("3", person3);
    region.put("4", person4);
    region.put("5", person5);

    final Person person6 = new Person(101L, "Rahul", "Rajiv", "Gndhi",
        DateTimeUtils.createDate(1970, Calendar.MAY, 14), Gender.MALE);
    final Person person7 = new Person(102L, "Narendra", "Damodar", "Modi",
        DateTimeUtils.createDate(1945, Calendar.DECEMBER, 24), Gender.MALE);
    final Person person8 = new Person(103L, "Atal", "Bihari", "Vajpayee",
        DateTimeUtils.createDate(1920, Calendar.AUGUST, 9), Gender.MALE);
    final Person person9 = new Person(104L, "Soniya", "Rajiv", "Gandhi",
        DateTimeUtils.createDate(1929, Calendar.MARCH, 27), Gender.FEMALE);
    final Person person10 = new Person(104L, "Priyanka", "Robert", "Gandhi",
        DateTimeUtils.createDate(1973, Calendar.APRIL, 15), Gender.FEMALE);

    final Person person11 = new Person(104L, "Murali", "Manohar", "Joshi",
        DateTimeUtils.createDate(1923, Calendar.APRIL, 25), Gender.MALE);
    final Person person12 = new Person(104L, "Lalkrishna", "Parmhansh", "Advani",
        DateTimeUtils.createDate(1910, Calendar.JANUARY, 1), Gender.MALE);
    final Person person13 = new Person(104L, "Shushma", "kumari", "Swaraj",
        DateTimeUtils.createDate(1943, Calendar.AUGUST, 10), Gender.FEMALE);
    final Person person14 = new Person(104L, "Arun", "raman", "jetly",
        DateTimeUtils.createDate(1942, Calendar.OCTOBER, 27), Gender.MALE);
    final Person person15 = new Person(104L, "Amit", "kumar", "shah",
        DateTimeUtils.createDate(1958, Calendar.DECEMBER, 21), Gender.MALE);
    final Person person16 = new Person(104L, "Shila", "kumari", "Dixit",
        DateTimeUtils.createDate(1927, Calendar.FEBRUARY, 15), Gender.FEMALE);

    Map<String, Object> userMap = new HashMap<>();
    userMap.put("6", person6);
    userMap.put("7", person7);
    userMap.put("8", person8);
    userMap.put("9", person9);
    userMap.put("10", person10);
    userMap.put("11", person11);
    userMap.put("12", person12);
    userMap.put("13", person13);
    userMap.put("14", person14);
    userMap.put("15", person15);
    userMap.put("16", person16);

    region.putAll(userMap);

    cache.getLogger().info("Gemfire Cache Client: Puts successfully done");
  }

  private void doQueryOpsUsingRestApis(String restEndpoint) throws IOException {
    // Query TestCase-1 :: Prepare parameterized Queries

    CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpPost post = new HttpPost(restEndpoint + findAllPeopleQuery);
    post.addHeader("Content-Type", "application/json");
    post.addHeader("Accept", "application/json");
    ClassicHttpResponse createNamedQueryResponse = httpclient.execute(post);
    assertThat(createNamedQueryResponse.getCode()).isEqualTo(201);
    assertThat(createNamedQueryResponse.getEntity()).isNotNull();
    createNamedQueryResponse.close();

    post = new HttpPost(restEndpoint + findPeopleByGenderQuery);
    post.addHeader("Content-Type", "application/json");
    post.addHeader("Accept", "application/json");
    createNamedQueryResponse = httpclient.execute(post);
    assertThat(createNamedQueryResponse.getCode()).isEqualTo(201);
    assertThat(createNamedQueryResponse.getEntity()).isNotNull();
    createNamedQueryResponse.close();

    post = new HttpPost(restEndpoint + findPeopleByLastNameQuery);
    post.addHeader("Content-Type", "application/json");
    post.addHeader("Accept", "application/json");
    createNamedQueryResponse = httpclient.execute(post);
    assertThat(createNamedQueryResponse.getCode()).isEqualTo(201);
    assertThat(createNamedQueryResponse.getEntity()).isNotNull();
    createNamedQueryResponse.close();

    // Query TestCase-2 :: List all parameterized queries

    HttpGet get = new HttpGet(restEndpoint + "/queries");
    httpclient = HttpClients.createDefault();
    ClassicHttpResponse listAllQueriesResponse = httpclient.execute(get);
    assertThat(listAllQueriesResponse.getCode()).isEqualTo(200);
    assertThat(listAllQueriesResponse.getEntity()).isNotNull();

    HttpEntity entity = listAllQueriesResponse.getEntity();
    InputStream content = entity.getContent();
    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
    String line;
    StringBuilder sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }
    listAllQueriesResponse.close();

    // Check whether received response contains expected query IDs.

    ObjectMapper mapper = new ObjectMapper();
    JsonNode jsonObject = mapper.readTree(sb.toString());
    JsonNode queries = jsonObject.get("queries");
    for (int i = 0; i < queries.size(); i++) {
      assertThat(Arrays.asList(PARAM_QUERY_IDS_ARRAY))
          .contains(queries.get(i).get("id").asText());
    }

    // Query TestCase-3 :: Run the specified named query passing in scalar values for query
    // parameters.

    httpclient = HttpClients.createDefault();
    post = new HttpPost(restEndpoint + "/queries/filterByLastName");
    post.addHeader("Content-Type", "application/json");
    post.addHeader("Accept", "application/json");
    entity = new StringEntity(QUERY_ARGS);
    post.setEntity(entity);
    ClassicHttpResponse runNamedQueryResponse = httpclient.execute(post);

    assertThat(runNamedQueryResponse.getCode()).isEqualTo(200);
    assertThat(runNamedQueryResponse.getEntity()).isNotNull();
  }

  private void verifyUpdatesInClientCache() {
    ClientCache cache = ClientCacheFactory.getAnyInstance();
    assertThat(cache).isNotNull();
    Region<String, Object> region = cache.getRegion(PEOPLE_REGION_NAME);

    Person expectedPerson = new Person(3L, "Nishka3", "Nilkanth3", "Patel3",
        DateTimeUtils.createDate(2009, Calendar.JULY, 31), Gender.FEMALE);
    Object value = region.get("3");

    assertThat(value).isInstanceOf(PdxInstance.class);

    PdxInstance pi3 = (PdxInstance) value;
    Person actualPerson = (Person) pi3.getObject();
    assertThat(actualPerson.getId()).isEqualTo(expectedPerson.getId());
    assertThat(actualPerson.getFirstName()).isEqualTo(expectedPerson.getFirstName());
    assertThat(actualPerson.getMiddleName()).isEqualTo(expectedPerson.getMiddleName());
    assertThat(actualPerson.getLastName()).isEqualTo(expectedPerson.getLastName());
    assertThat(actualPerson.getBirthDate()).isEqualTo(expectedPerson.getBirthDate());
    assertThat(actualPerson.getGender()).isEqualTo(expectedPerson.getGender());

    // verify update on key "2"

    expectedPerson = new Person(501L, "Barack", "Hussein", "Obama",
        DateTimeUtils.createDate(1961, Calendar.APRIL, 8), Gender.MALE);
    value = region.get("2");

    assertThat(value).isInstanceOf(PdxInstance.class);

    pi3 = (PdxInstance) value;
    actualPerson = (Person) pi3.getObject();
    assertThat(actualPerson.getId()).isEqualTo(expectedPerson.getId());
    assertThat(actualPerson.getFirstName()).isEqualTo(expectedPerson.getFirstName());
    assertThat(actualPerson.getMiddleName()).isEqualTo(expectedPerson.getMiddleName());
    assertThat(actualPerson.getLastName()).isEqualTo(expectedPerson.getLastName());
    assertThat(actualPerson.getBirthDate()).isEqualTo(expectedPerson.getBirthDate());
    assertThat(actualPerson.getGender()).isEqualTo(expectedPerson.getGender());

    // verify Deleted key "13"

    Object obj = region.get("13");
    assertThat(obj).isNull();

    obj = region.get("14");
    assertThat(obj).isNull();

    obj = region.get("15");
    assertThat(obj).isNull();

    obj = region.get("16");
    assertThat(obj).isNull();
  }

  private void doUpdatesUsingRestApis(String restEndpoint) throws IOException {
    // Update keys using REST calls

    CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpPut put = new HttpPut(restEndpoint + "/People/3,4,5,6,7,8,9,10,11,12");
    put.addHeader("Content-Type", "application/json");
    put.addHeader("Accept", "application/json");
    StringEntity entity = new StringEntity(PERSON_LIST_AS_JSON);
    put.setEntity(entity);
    ClassicHttpResponse result = httpclient.execute(put);
    assertThat(result).isNotNull();

    // Delete Single keys

    httpclient = HttpClients.createDefault();
    HttpDelete delete = new HttpDelete(restEndpoint + "/People/13");
    delete.addHeader("Content-Type", "application/json");
    delete.addHeader("Accept", "application/json");
    result = httpclient.execute(delete);
    assertThat(result).isNotNull();

    // Delete set of keys

    httpclient = HttpClients.createDefault();
    delete = new HttpDelete(restEndpoint + "/People/14,15,16");
    delete.addHeader("Content-Type", "application/json");
    delete.addHeader("Accept", "application/json");
    result = httpclient.execute(delete);
    assertThat(result).isNotNull();

    // REST put?op=CAS for key 1

    // REST put?op=REPLACE for key 2

    httpclient = HttpClients.createDefault();
    put = new HttpPut(restEndpoint + "/People/2?op=replace");
    put.addHeader("Content-Type", "application/json");
    put.addHeader("Accept", "application/json");
    entity = new StringEntity(PERSON_AS_JSON_REPLACE);
    put.setEntity(entity);
    result = httpclient.execute(put);
    assertThat(result).isNotNull();
  }

  private void fetchRestServerEndpoints(String restEndpoint) throws IOException {
    HttpGet get = new HttpGet(restEndpoint + "/servers");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    CloseableHttpClient httpclient = HttpClients.createDefault();

    ClassicHttpResponse response = httpclient.execute(get);
    HttpEntity entity = response.getEntity();
    InputStream content = entity.getContent();
    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
    String line;
    StringBuilder sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    // validate the status code
    assertThat(response.getCode()).isEqualTo(200);

    ObjectMapper mapper = new ObjectMapper();
    JsonNode jsonArray = mapper.readTree(sb.toString());

    // verify total number of REST service endpoints in DS
    assertThat(jsonArray.size()).isEqualTo(2);
  }

  private void doGetsUsingRestApis(String restEndpoint) throws IOException {
    // 1. Get on key="1" and validate result.

    HttpGet get = new HttpGet(restEndpoint + "/People/1");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    CloseableHttpClient httpclient = HttpClients.createDefault();
    ClassicHttpResponse response = httpclient.execute(get);

    HttpEntity entity = response.getEntity();
    InputStream content = entity.getContent();
    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
    String line;
    StringBuilder sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    ObjectMapper mapper = new ObjectMapper();
    JsonNode json = mapper.readTree(sb.toString());

    assertThat(json.get("id").asInt()).isEqualTo(101);
    assertThat(json.get("firstName").asText()).isEqualTo("Mithali");
    assertThat(json.get("middleName").asText()).isEqualTo("Dorai");
    assertThat(json.get("lastName").asText()).isEqualTo("Raj");
    assertThat(json.get("gender").asText()).isEqualTo(Gender.FEMALE.name());

    // 2. Get on key="16" and validate result.

    get = new HttpGet(restEndpoint + "/People/16");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    httpclient = HttpClients.createDefault();
    response = httpclient.execute(get);

    entity = response.getEntity();
    content = entity.getContent();
    reader = new BufferedReader(new InputStreamReader(content));
    sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    json = mapper.readTree(sb.toString());

    assertThat(json.get("id").asInt()).isEqualTo(104);
    assertThat(json.get("firstName").asText()).isEqualTo("Shila");
    assertThat(json.get("middleName").asText()).isEqualTo("kumari");
    assertThat(json.get("lastName").asText()).isEqualTo("Dixit");
    assertThat(json.get("gender").asText()).isEqualTo(Gender.FEMALE.name());

    // 3. Get all (getAll) entries in Region

    get = new HttpGet(restEndpoint + "/People");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    httpclient = HttpClients.createDefault();
    ClassicHttpResponse result = httpclient.execute(get);
    assertThat(result.getCode()).isEqualTo(200);
    assertThat(result.getEntity()).isNotNull();

    entity = result.getEntity();
    content = entity.getContent();
    reader = new BufferedReader(new InputStreamReader(content));
    sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }
    result.close();

    json = mapper.readTree(sb.toString());
    assertThat(json.get("People").size()).isEqualTo(16);

    // 4. GetAll?limit=10 (10 entries) and verify results

    get = new HttpGet(restEndpoint + "/People?limit=10");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    httpclient = HttpClients.createDefault();
    response = httpclient.execute(get);
    assertThat(response.getCode()).isEqualTo(200);
    assertThat(response.getEntity()).isNotNull();

    entity = response.getEntity();
    content = entity.getContent();
    reader = new BufferedReader(new InputStreamReader(content));
    sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    json = mapper.readTree(sb.toString());
    assertThat(json.get("People").size()).isEqualTo(10);

    // 5. Get keys - List all keys in region

    get = new HttpGet(restEndpoint + "/People/keys");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    httpclient = HttpClients.createDefault();
    response = httpclient.execute(get);
    assertThat(response.getCode()).isEqualTo(200);
    assertThat(response.getEntity()).isNotNull();

    entity = response.getEntity();
    content = entity.getContent();
    reader = new BufferedReader(new InputStreamReader(content));
    sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    json = mapper.readTree(sb.toString());
    assertThat(json.get("keys").size()).isEqualTo(16);

    // 6. Get data for specific keys

    get = new HttpGet(restEndpoint + "/People/1,3,5,7,9,11");
    get.addHeader("Content-Type", "application/json");
    get.addHeader("Accept", "application/json");
    httpclient = HttpClients.createDefault();
    response = httpclient.execute(get);
    assertThat(response.getCode()).isEqualTo(200);
    assertThat(response.getEntity()).isNotNull();

    entity = response.getEntity();
    content = entity.getContent();
    reader = new BufferedReader(new InputStreamReader(content));
    sb = new StringBuilder();
    while ((line = reader.readLine()) != null) {
      sb.append(line);
    }

    json = mapper.readTree(sb.toString());
    assertThat(json.get("People").size()).isEqualTo(6);
  }

  private void createRegionInClientCache() {
    ClientCache cache = ClientCacheFactory.getAnyInstance();
    assertThat(cache).isNotNull();
    ClientRegionFactory<String, Object> crf =
        cache.createClientRegionFactory(ClientRegionShortcut.PROXY);
    crf.create(PEOPLE_REGION_NAME);
  }

  private void createRegion() {
    Cache cache = CacheFactory.getAnyInstance();
    assertThat(cache).isNotNull();

    RegionFactory<String, Object> rf = cache.createRegionFactory(RegionShortcut.REPLICATE);
    rf.create(PEOPLE_REGION_NAME);
  }

  private void createClientCache(final String host, final int port) {
    // Connect using the GemFire locator and create a Caching_Proxy cache
    ClientCache clientCache =
        new ClientCacheFactory().setPdxReadSerialized(true).addPoolLocator(host, port).create();

    clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY).create(REGION_NAME);
  }

  /**
   * InterOps Test between REST-client, Peer Cache Client and Client Cache
   */
  @Test
  public void testInterOpsWithReplicatedRegion() throws Exception {
    VM locator = VM.getVM(0);
    VM manager = VM.getVM(1);
    VM server = VM.getVM(2);
    VM client = VM.getVM(3);

    // start locator
    final String hostName = NetworkUtils.getServerHostName();
    int locatorPort = locator.invoke(() -> startLocator(hostName, ""));

    // find locators
    String locators = hostName + "[" + locatorPort + "]";

    // start manager (peer cache)
    manager.invoke(() -> startManager(locators, new String[] {REGION_NAME}));

    // start startCacheServer With RestService enabled
    final String serverHostName = server.getHost().getHostName();
    String restEndpoint = server.invoke(() -> startBridgeServerWithRestService(serverHostName,
        locators, new String[] {REGION_NAME}));

    // create a client cache
    client.invoke(() -> createClientCache(hostName, locatorPort));

    // create region in Manager, peer cache and Client cache nodes
    manager.invoke(this::createRegion);
    server.invoke(this::createRegion);
    client.invoke(this::createRegionInClientCache);

    // do some person puts from clientcache
    client.invoke(this::doPutsInClientCache);

    // TEST: fetch all available REST endpoints
    fetchRestServerEndpoints(restEndpoint);

    // Controller VM - config REST Client and make HTTP calls
    doGetsUsingRestApis(restEndpoint);

    // update Data using REST APIs
    doUpdatesUsingRestApis(restEndpoint);

    client.invoke(this::verifyUpdatesInClientCache);

    // Querying
    doQueryOpsUsingRestApis(restEndpoint);
  }
}
