Repository: ignite
Updated Branches:
  refs/heads/master a84c900fb -> a84018b21


IGNITE-7803 REST: Implemented possibility to get values from cache inserted via 
API or SQL.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/a84018b2
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/a84018b2
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/a84018b2

Branch: refs/heads/master
Commit: a84018b2102575cabbffe2e5ec5e9ca19148460a
Parents: a84c900
Author: Alexey Kuznetsov <akuznet...@apache.org>
Authored: Fri Mar 2 16:35:26 2018 +0700
Committer: Alexey Kuznetsov <akuznet...@apache.org>
Committed: Fri Mar 2 16:35:26 2018 +0700

----------------------------------------------------------------------
 .../JettyRestProcessorAbstractSelfTest.java     | 305 ++++++++++++++-----
 .../internal/processors/rest/SimplePerson.java  |   4 +-
 .../internal/binary/BinaryObjectExImpl.java     |  69 ++++-
 .../handlers/cache/GridCacheCommandHandler.java |  14 +-
 .../http/jetty/GridJettyObjectMapper.java       |  61 +++-
 .../http/jetty/GridJettyRestHandler.java        |  39 ++-
 6 files changed, 399 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
index e4433cf..ece467a 100644
--- 
a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
+++ 
b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java
@@ -47,6 +47,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cache.query.SqlQuery;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.cluster.ClusterNode;
@@ -166,9 +167,6 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     /** Grid count. */
     private static final int GRID_CNT = 3;
 
-    /** Url address to send HTTP request. */
-    private final String TEST_URL = "http://"; + LOC_HOST + ":" + restPort() + 
"/ignite?";
-
     /** Used to sent request charset. */
     private static final String CHARSET = StandardCharsets.UTF_8.name();
 
@@ -207,6 +205,13 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     protected abstract int restPort();
 
     /**
+     * @return Test URL
+     */
+    protected String restUrl() {
+        return "http://"; + LOC_HOST + ":" + restPort() + "/ignite?";
+    }
+
+    /**
      * @return Security enabled flag. Should be the same with {@code 
ctx.security().enabled()}.
      */
     protected boolean securityEnabled() {
@@ -214,12 +219,14 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     }
 
     /**
+     * Execute REST command and return result.
+     *
      * @param params Command parameters.
      * @return Returned content.
      * @throws Exception If failed.
      */
     protected String content(Map<String, String> params) throws Exception {
-        SB sb = new SB(TEST_URL);
+        SB sb = new SB(restUrl());
 
         for (Map.Entry<String, String> e : params.entrySet())
             sb.a(e.getKey()).a('=').a(e.getValue()).a('&');
@@ -272,35 +279,17 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
     /**
      * @param content Content to check.
-     */
-    private void assertResponseContainsError(String content) throws 
IOException {
-        assertNotNull(content);
-        assertFalse(content.isEmpty());
-
-        JsonNode node = JSON_MAPPER.readTree(content);
-
-        assertEquals(1, node.get("successStatus").asInt());
-        assertFalse(node.get("error").isNull());
-        assertTrue(node.get("response").isNull());
-        assertTrue(node.get("sessionToken").isNull());
-    }
-
-    /**
-     * @param content Content to check.
      * @param err Error message.
      */
     private void assertResponseContainsError(String content, String err) 
throws IOException {
-        assertNotNull(content);
-        assertFalse(content.isEmpty());
-
+        assertFalse(F.isEmpty(content));
         assertNotNull(err);
 
         JsonNode node = JSON_MAPPER.readTree(content);
 
-        assertEquals(1, node.get("successStatus").asInt());
-
+        assertEquals(STATUS_FAILED, node.get("successStatus").asInt());
         assertTrue(node.get("response").isNull());
-        assertEquals(err, node.get("error").asText());
+        assertTrue(node.get("error").asText().contains(err));
     }
 
     /**
@@ -313,7 +302,7 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
         JsonNode node = JSON_MAPPER.readTree(content);
 
         assertEquals(bulk, node.get("affinityNodeId").isNull());
-        assertEquals(0, node.get("successStatus").asInt());
+        assertEquals(STATUS_SUCCESS, node.get("successStatus").asInt());
         assertTrue(node.get("error").isNull());
 
         assertNotSame(securityEnabled(), node.get("sessionToken").isNull());
@@ -328,7 +317,7 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     private void assertCacheOperation(String content, Object res) throws 
IOException {
         JsonNode ret = jsonCacheOperationResponse(content, false);
 
-        assertEquals(String.valueOf(res), ret.asText());
+        assertEquals(String.valueOf(res), ret.isObject() ? ret.toString() : 
ret.asText());
     }
 
     /**
@@ -409,6 +398,116 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     }
 
     /**
+     * @param json JSON to check.
+     * @param p Person to compare with.
+     * @throws IOException If failed.
+     */
+    private void checkJson(String json, Person p) throws IOException {
+        JsonNode res = jsonCacheOperationResponse(json, false);
+
+        assertEquals(p.id.intValue(), res.get("id").asInt());
+        assertEquals(p.getOrganizationId().intValue(), 
res.get("orgId").asInt());
+        assertEquals(p.getFirstName(), res.get("firstName").asText());
+        assertEquals(p.getLastName(), res.get("lastName").asText());
+        assertEquals(p.getSalary(), res.get("salary").asDouble());
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetBinaryObjects() throws Exception {
+        Person p = new Person(1, "John", "Doe", 300);
+
+        jcache().put(300, p);
+
+        String ret = content(DEFAULT_CACHE_NAME, GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "300"
+        );
+
+        info("Get command result: " + ret);
+
+        checkJson(ret, p);
+
+        // Test with remote node.
+        ret = content(DEFAULT_CACHE_NAME, GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "300",
+            "destId", grid(1).localNode().id().toString()
+        );
+
+        info("Get command result: " + ret);
+
+        checkJson(ret, p);
+
+        // Test with SQL.
+        SqlFieldsQuery qry = new SqlFieldsQuery(
+            "create table employee(id integer primary key, name varchar(100), 
salary integer);" +
+            "insert into employee(id, name, salary) values (1, 'Alex', 300);"
+        );
+
+        grid(0).context().query().querySqlFields(qry, true, false);
+
+        ret = content("SQL_PUBLIC_EMPLOYEE", GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "1"
+        );
+
+        info("Get command result: " + ret);
+
+        JsonNode res = jsonCacheOperationResponse(ret, false);
+
+        assertEquals("Alex", res.get("NAME").asText());
+        assertEquals(300, res.get("SALARY").asInt());
+
+        // Test with circular reference.
+        CircularRef ref1 = new CircularRef(1, "Alex");
+        CircularRef ref2 = new CircularRef(2, "300");
+        CircularRef ref3 = new CircularRef(3, "220");
+
+        ref1.ref(ref2);
+
+        jcache().put(220, ref1);
+
+        ret = content(DEFAULT_CACHE_NAME, GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "220"
+        );
+
+        info("Get command result: " + ret);
+
+        JsonNode json = jsonCacheOperationResponse(ret, false);
+        assertEquals(ref1.name, json.get("name").asText());
+
+        ref2.ref(ref1);
+
+        jcache().put(222, ref1);
+
+        ret = content(DEFAULT_CACHE_NAME, GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "222"
+        );
+
+        info("Get command result: " + ret);
+
+        assertResponseContainsError(ret, "Failed convert to JSON object for 
circular references");
+
+        ref1.ref(ref2);
+        ref2.ref(ref3);
+        ref3.ref(ref1);
+        jcache().put(223, ref1);
+
+        ret = content(DEFAULT_CACHE_NAME, GridRestCommand.CACHE_GET,
+            "keyType", "int",
+            "key", "223"
+        );
+
+        info("Get command result: " + ret);
+
+        assertResponseContainsError(ret, "Failed convert to JSON object for 
circular references");
+    }
+
+    /**
      * @throws Exception If failed.
      */
     public void testNullMapKeyAndValue() throws Exception {
@@ -455,13 +554,13 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         JsonNode res = jsonCacheOperationResponse(ret, false);
 
-        assertEquals(res.get("id").asInt(), p.id);
-        assertEquals(res.get("name").asText(), p.name);
-        assertEquals(res.get("birthday").asText(), p.birthday.toString());
-        assertEquals(res.get("salary").asDouble(), p.salary);
+        assertEquals(p.id, res.get("id").asInt());
+        assertEquals(p.name, res.get("name").asText());
+        assertEquals(p.birthday.toString(), res.get("birthday").asText());
+        assertEquals(p.salary, res.get("salary").asDouble());
         assertNull(res.get("age"));
-        assertNull(res.get("post"));
-        assertNull(res.get("bonus"));
+        assertEquals(p.post, res.get("post").asText());
+        assertEquals(25, res.get("bonus").asInt());
     }
 
     /**
@@ -582,7 +681,7 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
         CacheMode mode,
         int backups,
         CacheWriteSynchronizationMode wrtSync,
-        String cacheGroup,
+        String cacheGrp,
         String dataRegion,
         String... params
     ) throws Exception {
@@ -600,8 +699,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
         assertEquals(mode, ccfg.getCacheMode());
         assertEquals(wrtSync, ccfg.getWriteSynchronizationMode());
 
-        if (!F.isEmpty(cacheGroup))
-            assertEquals(cacheGroup, ccfg.getGroupName());
+        if (!F.isEmpty(cacheGrp))
+            assertEquals(cacheGrp, ccfg.getGroupName());
 
         if (!F.isEmpty(dataRegion))
             assertEquals(dataRegion, ccfg.getDataRegionName());
@@ -1338,7 +1437,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         testMetadata(dfltCacheMeta, arrRes);
 
-        assertResponseContainsError(content("nonExistingCacheName", 
GridRestCommand.CACHE_METADATA));
+        assertResponseContainsError(content("nonExistingCacheName", 
GridRestCommand.CACHE_METADATA),
+            "Failed to request meta data. nonExistingCacheName is not found");
     }
 
     /**
@@ -1376,7 +1476,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         testMetadata(metas, arrRes);
 
-        assertResponseContainsError(content("nonExistingCacheName", 
GridRestCommand.CACHE_METADATA));
+        assertResponseContainsError(content("nonExistingCacheName", 
GridRestCommand.CACHE_METADATA),
+            "Failed to request meta data. nonExistingCacheName is not found");
     }
 
     /**
@@ -1482,14 +1583,14 @@ public abstract class 
JettyRestProcessorAbstractSelfTest extends AbstractRestPro
 
         info("Exe command result: " + ret);
 
-        assertResponseContainsError(ret);
+        assertResponseContainsError(ret, "Failed to find mandatory parameter 
in request: name");
 
         // Attempt to execute unknown task (UNKNOWN_TASK) will result in 
exception on server.
         ret = content(DEFAULT_CACHE_NAME, GridRestCommand.EXE, "name", 
"UNKNOWN_TASK");
 
         info("Exe command result: " + ret);
 
-        assertResponseContainsError(ret);
+        assertResponseContainsError(ret, "Unknown task name or failed to 
auto-deploy task (was task (re|un)deployed?)");
 
         grid(0).compute().localDeployTask(TestTask1.class, 
TestTask1.class.getClassLoader());
         grid(0).compute().localDeployTask(TestTask2.class, 
TestTask2.class.getClassLoader());
@@ -1514,7 +1615,7 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         info("Exe command result: " + ret);
 
-        assertResponseContainsError(ret);
+        assertResponseContainsError(ret, "Failed to find mandatory parameter 
in request: id");
     }
 
     /**
@@ -2190,8 +2291,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Boolean, Boolean> cBool = typedCache();
 
-        assertEquals(cBool.get(true), Boolean.FALSE);
-        assertEquals(cBool.get(false), Boolean.TRUE);
+        assertEquals(Boolean.FALSE, cBool.get(true));
+        assertEquals(Boolean.TRUE, cBool.get(false));
 
         // Test byte type.
         putTypedValue("byte", "64", "100", STATUS_SUCCESS);
@@ -2202,8 +2303,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Byte, Byte> cByte = typedCache();
 
-        assertEquals(cByte.get(Byte.valueOf("64")), Byte.valueOf("100"));
-        assertEquals(cByte.get(Byte.valueOf("-25")), Byte.valueOf("-127"));
+        assertEquals(Byte.valueOf("100"), cByte.get(Byte.valueOf("64")));
+        assertEquals(Byte.valueOf("-127"), cByte.get(Byte.valueOf("-25")));
 
         // Test short type.
         putTypedValue("short", "1024", "4096", STATUS_SUCCESS);
@@ -2214,8 +2315,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Short, Short> cShort = typedCache();
 
-        assertEquals(cShort.get(Short.valueOf("1024")), Short.valueOf("4096"));
-        assertEquals(cShort.get(Short.valueOf("-15000")), 
Short.valueOf("-16000"));
+        assertEquals(Short.valueOf("4096"), cShort.get(Short.valueOf("1024")));
+        assertEquals(Short.valueOf("-16000"), 
cShort.get(Short.valueOf("-15000")));
 
         // Test integer type.
         putTypedValue("int", "65555", "128256", STATUS_SUCCESS);
@@ -2227,9 +2328,9 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Integer, Integer> cInt = typedCache();
 
-        assertEquals(cInt.get(65555), Integer.valueOf(128256));
-        assertEquals(cInt.get(74555), Integer.valueOf(200000));
-        assertEquals(cInt.get(-200), Integer.valueOf(-100000));
+        assertEquals(Integer.valueOf(128256), cInt.get(65555));
+        assertEquals(Integer.valueOf(200000), cInt.get(74555));
+        assertEquals(Integer.valueOf(-100000), cInt.get(-200));
 
         // Test long type.
         putTypedValue("long", "3000000", "400000", STATUS_SUCCESS);
@@ -2240,8 +2341,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Long, Long> cLong = typedCache();
 
-        assertEquals(cLong.get(3000000L), Long.valueOf(400000));
-        assertEquals(cLong.get(-3000000L), Long.valueOf(-400000));
+        assertEquals(Long.valueOf(400000), cLong.get(3000000L));
+        assertEquals(Long.valueOf(-400000), cLong.get(-3000000L));
 
         // Test float type.
         putTypedValue("float", "1.5", "2.5", STATUS_SUCCESS);
@@ -2252,8 +2353,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Float, Float> cFloat = typedCache();
 
-        assertEquals(cFloat.get(1.5f), 2.5f);
-        assertEquals(cFloat.get(-7.5f), -8.5f);
+        assertEquals(2.5f, cFloat.get(1.5f));
+        assertEquals(-8.5f, cFloat.get(-7.5f));
 
         // Test double type.
         putTypedValue("double", "5.5", "75.5", STATUS_SUCCESS);
@@ -2264,8 +2365,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Double, Double> cDouble = typedCache();
 
-        assertEquals(cDouble.get(5.5d), 75.5d);
-        assertEquals(cDouble.get(-155.5d), -255.5d);
+        assertEquals(75.5d, cDouble.get(5.5d));
+        assertEquals(-255.5d, cDouble.get(-155.5d));
 
         // Test date type.
         putTypedValue("date", "2018-02-18", "2017-01-01", STATUS_SUCCESS);
@@ -2276,8 +2377,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Date, Date> cDate = typedCache();
 
-        assertEquals(cDate.get(Date.valueOf("2018-02-18")), 
Date.valueOf("2017-01-01"));
-        assertEquals(cDate.get(Date.valueOf("2018-01-01")), 
Date.valueOf("2017-02-02"));
+        assertEquals(Date.valueOf("2017-01-01"), 
cDate.get(Date.valueOf("2018-02-18")));
+        assertEquals(Date.valueOf("2017-02-02"), 
cDate.get(Date.valueOf("2018-01-01")));
 
         // Test time type.
         putTypedValue("Time", "01:01:01", "02:02:02", STATUS_SUCCESS);
@@ -2288,8 +2389,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Time, Time> cTime = typedCache();
 
-        assertEquals(cTime.get(Time.valueOf("01:01:01")), 
Time.valueOf("02:02:02"));
-        assertEquals(cTime.get(Time.valueOf("03:03:03")), 
Time.valueOf("04:04:04"));
+        assertEquals(Time.valueOf("02:02:02"), 
cTime.get(Time.valueOf("01:01:01")));
+        assertEquals(Time.valueOf("04:04:04"), 
cTime.get(Time.valueOf("03:03:03")));
 
         // Test timestamp type.
         putTypedValue("Timestamp", "2018-02-18%2001:01:01", 
"2017-01-01%2002:02:02", STATUS_SUCCESS);
@@ -2300,8 +2401,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<Timestamp, Timestamp> cTimestamp = typedCache();
 
-        assertEquals(cTimestamp.get(Timestamp.valueOf("2018-02-18 01:01:01")), 
Timestamp.valueOf("2017-01-01 02:02:02"));
-        assertEquals(cTimestamp.get(Timestamp.valueOf("2018-01-01 01:01:01")), 
Timestamp.valueOf("2018-05-05 05:05:05"));
+        assertEquals(Timestamp.valueOf("2017-01-01 02:02:02"), 
cTimestamp.get(Timestamp.valueOf("2018-02-18 01:01:01")));
+        assertEquals(Timestamp.valueOf("2018-05-05 05:05:05"), 
cTimestamp.get(Timestamp.valueOf("2018-01-01 01:01:01")));
 
         // Test UUID type.
         UUID k1 = UUID.fromString("121f5ae8-148d-11e8-b642-0ed5f89f718b");
@@ -2317,8 +2418,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<UUID, UUID> cUUID = typedCache();
 
-        assertEquals(cUUID.get(k1), v1);
-        assertEquals(cUUID.get(k2), v2);
+        assertEquals(v1, cUUID.get(k1));
+        assertEquals(v2, cUUID.get(k2));
 
         // Test IgniteUuid type.
         IgniteUuid ik1 = IgniteUuid.randomUuid();
@@ -2334,8 +2435,8 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         IgniteCache<IgniteUuid, IgniteUuid> cIgniteUUID = typedCache();
 
-        assertEquals(cIgniteUUID.get(ik1), iv1);
-        assertEquals(cIgniteUUID.get(ik2), iv2);
+        assertEquals(iv1, cIgniteUUID.get(ik1));
+        assertEquals(iv2, cIgniteUUID.get(ik2));
     }
 
     /**
@@ -2352,7 +2453,9 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         info("Command result: " + ret);
 
-        assertEquals(exp, jsonResponse(ret).asText());
+        JsonNode json = jsonResponse(ret);
+
+        assertEquals(exp, json.isObject() ? json.toString() : json.asText());
     }
 
     /**
@@ -2464,7 +2567,6 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
         getTypedValue("UUID", k1.toString(), v1.toString());
         getTypedValue("java.util.UUID", k2.toString(), v2.toString());
 
-
         // Test IgniteUuid type.
         IgniteCache<IgniteUuid, IgniteUuid> cIgniteUUID = typedCache();
 
@@ -2478,6 +2580,22 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
 
         getTypedValue("IgniteUuid", ik1.toString(), iv1.toString());
         getTypedValue("org.apache.ignite.lang.IgniteUuid", ik2.toString(), 
iv2.toString());
+
+        // Test tuple.
+        IgniteCache<Integer, T2<Integer, String>> cTuple = typedCache();
+
+        T2<Integer, String> tup = new T2<>(1, "test");
+
+        cTuple.put(555, tup);
+
+        getTypedValue("int", "555", JSON_MAPPER.writeValueAsString(tup));
+
+        // Test enum.
+        IgniteCache<Integer, CacheMode> cEnum = typedCache();
+
+        cEnum.put(888, PARTITIONED);
+
+        getTypedValue("int", "888", PARTITIONED.toString());
     }
 
     /**
@@ -2589,6 +2707,57 @@ public abstract class JettyRestProcessorAbstractSelfTest 
extends AbstractRestPro
     }
 
     /**
+     * Test class that could have circular references.
+     */
+    public static class CircularRef implements Serializable {
+        /** */
+        private int id;
+
+        /** */
+        private String name;
+
+        /** */
+        private CircularRef ref;
+
+        /**
+         * @param id ID.
+         * @param name Name.
+         */
+        CircularRef(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        /**
+         * @return ID.
+         */
+        public int id() {
+            return id;
+        }
+
+        /**
+         * @return Name.
+         */
+        public String name() {
+            return name;
+        }
+
+        /**
+         * @return Reference to other object.
+         */
+        public CircularRef ref() {
+            return ref;
+        }
+
+        /**
+         * @param ref Reference to other object.
+         */
+        public void ref(CircularRef ref) {
+            this.ref = ref;
+        }
+    }
+
+    /**
      * Person class.
      */
     public static class Person implements Serializable {

http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/SimplePerson.java
----------------------------------------------------------------------
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/SimplePerson.java
 
b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/SimplePerson.java
index 0185213..3a157d7 100644
--- 
a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/SimplePerson.java
+++ 
b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/SimplePerson.java
@@ -38,10 +38,10 @@ public class SimplePerson {
     /** Must be excluded on serialization. */
     public transient int age;
 
-    /** Must be excluded on serialization. */
+    /** Post. */
     protected String post;
 
-    /** Must be excluded on serialization. */
+    /** Bonus. */
     private int bonus;
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectExImpl.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectExImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectExImpl.java
index b5e066b..c5fe6da 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectExImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectExImpl.java
@@ -105,9 +105,9 @@ public abstract class BinaryObjectExImpl implements 
BinaryObjectEx {
     @Nullable public abstract <F> F fieldByOrder(int order);
 
     /**
-     * Create field comparer.
+     * Create field comparator.
      *
-     * @return Comparer.
+     * @return Comparator.
      */
     public abstract BinarySerializedFieldComparator createFieldComparator();
 
@@ -245,7 +245,7 @@ public abstract class BinaryObjectExImpl implements 
BinaryObjectEx {
     private void appendValue(Object val, SB buf, BinaryReaderHandles ctx,
         IdentityHashMap<BinaryObject, Integer> handles) {
         if (val instanceof byte[])
-            buf.a(Arrays.toString((byte[]) val));
+            buf.a(Arrays.toString((byte[])val));
         else if (val instanceof short[])
             buf.a(Arrays.toString((short[])val));
         else if (val instanceof int[])
@@ -259,7 +259,7 @@ public abstract class BinaryObjectExImpl implements 
BinaryObjectEx {
         else if (val instanceof char[])
             buf.a(Arrays.toString((char[])val));
         else if (val instanceof boolean[])
-            buf.a(Arrays.toString((boolean[]) val));
+            buf.a(Arrays.toString((boolean[])val));
         else if (val instanceof BigDecimal[])
             buf.a(Arrays.toString((BigDecimal[])val));
         else if (val instanceof IgniteUuid)
@@ -336,4 +336,65 @@ public abstract class BinaryObjectExImpl implements 
BinaryObjectEx {
         else
             buf.a(val);
     }
+
+    /**
+     * Check if object graph has circular references.
+     *
+     * @return {@code true} if object has circular references.
+     */
+    public boolean hasCircularReferences() {
+        try {
+            BinaryReaderHandles ctx = new BinaryReaderHandles();
+
+            ctx.put(start(), this);
+
+            return hasCircularReferences(ctx, new 
IdentityHashMap<BinaryObject, Integer>());
+        }
+        catch (BinaryObjectException e) {
+            throw new IgniteException("Failed to check binary object for 
circular references", e);
+        }
+    }
+
+    /**
+     * @param ctx Reader context.
+     * @param handles Handles for already traversed objects.
+     * @return {@code true} if has circular reference.
+     */
+    private boolean hasCircularReferences(BinaryReaderHandles ctx, 
IdentityHashMap<BinaryObject, Integer> handles) {
+        BinaryType meta;
+
+        try {
+            meta = rawType();
+        }
+        catch (BinaryObjectException ignore) {
+            meta = null;
+        }
+
+        if (meta == null)
+            return false;
+
+        int idHash = System.identityHashCode(this);
+
+        handles.put(this, idHash);
+
+        if (meta.fieldNames() != null) {
+            ctx.put(start(), this);
+
+            for (String name : meta.fieldNames()) {
+                Object val = field(ctx, name);
+
+                if (val instanceof BinaryObjectExImpl) {
+                    BinaryObjectExImpl po = (BinaryObjectExImpl)val;
+
+                    Integer idHash0 = handles.get(val);
+
+                    // Check for circular reference.
+                    if (idHash0 != null || po.hasCircularReferences(ctx, 
handles))
+                        return true;
+                }
+            }
+        }
+
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
index f9f2cc3..0a545b4 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cache/GridCacheCommandHandler.java
@@ -35,6 +35,7 @@ import javax.cache.processor.EntryProcessor;
 import javax.cache.processor.EntryProcessorException;
 import javax.cache.processor.EntryProcessorResult;
 import javax.cache.processor.MutableEntry;
+
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
@@ -758,7 +759,10 @@ public class GridCacheCommandHandler extends 
GridRestCommandHandlerAdapter {
             destId == null || destId.equals(ctx.localNodeId()) || 
replicatedCacheAvailable(cacheName);
 
         if (locExec) {
-            IgniteInternalCache<?, ?> prj = 
localCache(cacheName).forSubjectId(clientId).setSkipStore(skipStore);
+            IgniteInternalCache<?, ?> prj = localCache(cacheName)
+                .forSubjectId(clientId)
+                .setSkipStore(skipStore)
+                .keepBinary();
 
             return op.apply((IgniteInternalCache<Object, Object>)prj, ctx).
                 chain(resultWrapper((IgniteInternalCache<Object, Object>)prj, 
key));
@@ -920,7 +924,8 @@ public class GridCacheCommandHandler extends 
GridRestCommandHandlerAdapter {
             String cacheName,
             boolean skipStore,
             CacheProjectionCommand op,
-            Object key) {
+            Object key
+        ) {
             this.clientId = clientId;
             this.cacheName = cacheName;
             this.skipStore = skipStore;
@@ -930,7 +935,10 @@ public class GridCacheCommandHandler extends 
GridRestCommandHandlerAdapter {
 
         /** {@inheritDoc} */
         @Override public GridRestResponse call() throws Exception {
-            IgniteInternalCache<?, ?> prj = cache(g, 
cacheName).forSubjectId(clientId).setSkipStore(skipStore);
+            IgniteInternalCache<?, ?> prj = cache(g, cacheName)
+                .forSubjectId(clientId)
+                .setSkipStore(skipStore)
+                .keepBinary();
 
             // Need to apply both operation and response transformation 
remotely
             // as cache could be inaccessible on local node and

http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java
----------------------------------------------------------------------
diff --git 
a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java
 
b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java
index 00941d0..d8b79cf 100644
--- 
a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java
+++ 
b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyObjectMapper.java
@@ -17,10 +17,14 @@
 
 package org.apache.ignite.internal.processors.rest.protocols.http.jetty;
 
+import java.io.IOException;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.util.Locale;
+
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.BeanProperty;
 import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationConfig;
@@ -29,10 +33,10 @@ import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
 import com.fasterxml.jackson.databind.ser.SerializerFactory;
-import java.io.IOException;
-import java.sql.SQLException;
-import java.text.DateFormat;
-import java.util.Locale;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryType;
+import org.apache.ignite.internal.binary.BinaryObjectImpl;
 import 
org.apache.ignite.internal.processors.cache.query.GridCacheSqlIndexMetadata;
 import org.apache.ignite.internal.processors.cache.query.GridCacheSqlMetadata;
 import org.apache.ignite.internal.util.typedef.F;
@@ -59,6 +63,7 @@ public class GridJettyObjectMapper extends ObjectMapper {
         module.addSerializer(IgniteUuid.class, IGNITE_UUID_SERIALIZER);
         module.addSerializer(GridCacheSqlMetadata.class, 
IGNITE_SQL_METADATA_SERIALIZER);
         module.addSerializer(GridCacheSqlIndexMetadata.class, 
IGNITE_SQL_INDEX_METADATA_SERIALIZER);
+        module.addSerializer(BinaryObjectImpl.class, 
IGNITE_BINARY_OBJECT_SERIALIZER);
 
         configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
 
@@ -89,7 +94,7 @@ public class GridJettyObjectMapper extends ObjectMapper {
          * Default constructor.
          */
         CustomSerializerProvider() {
-            super();
+            // No-op.
         }
 
         /**
@@ -109,13 +114,12 @@ public class GridJettyObjectMapper extends ObjectMapper {
         }
 
         /** {@inheritDoc} */
-        @Override public JsonSerializer<Object> findNullKeySerializer(JavaType 
serializationType,
-            BeanProperty prop) throws JsonMappingException {
+        @Override public JsonSerializer<Object> findNullKeySerializer(JavaType 
serializationType, BeanProperty prop) {
             return NULL_KEY_SERIALIZER;
         }
 
         /** {@inheritDoc} */
-        @Override public JsonSerializer<Object> 
findNullValueSerializer(BeanProperty prop) throws JsonMappingException {
+        @Override public JsonSerializer<Object> 
findNullValueSerializer(BeanProperty prop) {
             return NULL_VALUE_SERIALIZER;
         }
     }
@@ -221,4 +225,43 @@ public class GridJettyObjectMapper extends ObjectMapper {
             gen.writeEndObject();
         }
     };
+
+    /** Custom serializer for {@link GridCacheSqlIndexMetadata} */
+    private static final JsonSerializer<BinaryObjectImpl> 
IGNITE_BINARY_OBJECT_SERIALIZER = new JsonSerializer<BinaryObjectImpl>() {
+        /** {@inheritDoc} */
+        @Override public void serialize(BinaryObjectImpl bin, JsonGenerator 
gen, SerializerProvider ser) throws IOException {
+            try {
+                BinaryType meta = bin.rawType();
+
+                // Serialize to JSON if we have metadata.
+                if (meta != null && !F.isEmpty(meta.fieldNames())) {
+                    gen.writeStartObject();
+
+                    for (String name : meta.fieldNames()) {
+                        Object val = bin.field(name);
+
+                        if (val instanceof BinaryObjectImpl) {
+                            BinaryObjectImpl ref = (BinaryObjectImpl)val;
+
+                            if (ref.hasCircularReferences())
+                                throw ser.mappingException("Failed convert to 
JSON object for circular references");
+                        }
+                        else
+                            gen.writeObjectField(name, val);
+                    }
+
+                    gen.writeEndObject();
+                }
+                else {
+                    // Otherwise serialize as Java object.
+                    Object obj = bin.deserialize();
+
+                    gen.writeObject(obj);
+                }
+            }
+            catch (BinaryObjectException ignore) {
+                gen.writeNull();
+            }
+        }
+    };
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/a84018b2/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
----------------------------------------------------------------------
diff --git 
a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
 
b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
index 0b24998..1d80eea 100644
--- 
a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
+++ 
b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
+import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
 import java.sql.Date;
@@ -100,6 +101,9 @@ public class GridJettyRestHandler extends AbstractHandler {
     /** */
     private static final String  TEMPLATE_NAME_PARAM = "templateName";
 
+    /** */
+    private static final NullOutputStream NULL_OUTPUT_STREAM = new 
NullOutputStream();
+
     /** Logger. */
     private final IgniteLogger log;
 
@@ -391,16 +395,17 @@ public class GridJettyRestHandler extends AbstractHandler 
{
             cmdRes = new GridRestResponse(STATUS_FAILED, e.getMessage());
         }
 
-        try {
-            ServletOutputStream os = res.getOutputStream();
-
+        try(ServletOutputStream os = res.getOutputStream()) {
             try {
+                // Try serialize.
+                jsonMapper.writeValue(NULL_OUTPUT_STREAM, cmdRes);
+
                 jsonMapper.writeValue(os, cmdRes);
             }
             catch (JsonProcessingException e) {
                 U.error(log, "Failed to convert response to JSON: " + cmdRes, 
e);
 
-                jsonMapper.writeValue(os, F.asMap("successStatus", 
STATUS_FAILED, "error", e.getMessage()));
+                jsonMapper.writeValue(os, new GridRestResponse(STATUS_FAILED, 
e.getMessage()));
             }
 
             if (log.isDebugEnabled())
@@ -525,10 +530,10 @@ public class GridJettyRestHandler extends AbstractHandler 
{
                 }
 
                 // Set cache group name.
-                String cacheGroup = (String)params.get(CACHE_GROUP_PARAM);
+                String cacheGrp = (String)params.get(CACHE_GROUP_PARAM);
 
-                if (!F.isEmpty(cacheGroup))
-                    cfg.cacheGroup(cacheGroup);
+                if (!F.isEmpty(cacheGrp))
+                    cfg.cacheGroup(cacheGrp);
 
                 // Set cache data region name.
                 String dataRegion = (String)params.get(DATA_REGION_PARAM);
@@ -922,4 +927,24 @@ public class GridJettyRestHandler extends AbstractHandler {
 
         return null;
     }
+
+    /**
+     * Special stream to check JSON serialization.
+     */
+    private static class NullOutputStream extends OutputStream {
+        /** {@inheritDoc} */
+        @Override public void write(byte[] b, int off, int len) {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @Override public void write(int b) {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @Override public void write(byte[] b) {
+            // No-op.
+        }
+    }
 }

Reply via email to