Java tutorial
/* * Copyright 2015-2017 UnboundID Corp. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License (GPLv2 only) * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <http://www.gnu.org/licenses>. */ package com.unboundid.scim2.common; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.unboundid.scim2.common.filters.Filter; import com.unboundid.scim2.common.messages.PatchOpType; import com.unboundid.scim2.common.messages.PatchOperation; import com.unboundid.scim2.common.types.Email; import com.unboundid.scim2.common.types.Entitlement; import com.unboundid.scim2.common.types.InstantMessagingAddress; import com.unboundid.scim2.common.types.Name; import com.unboundid.scim2.common.types.PhoneNumber; import com.unboundid.scim2.common.types.Photo; import com.unboundid.scim2.common.utils.JsonUtils; import org.testng.Assert; import org.testng.annotations.Test; import java.io.IOException; import java.math.BigDecimal; import java.net.URI; import java.util.Iterator; import java.util.List; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; /** * Test the SCIM resource diff utility. */ public class DiffTestCase { /** * Test comparison of single-valued attributes. * * @throws Exception if an error occurs. */ @Test public void testDiffSingularAttribute() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); // - unchanged source.put("userName", "bjensen"); target.put("userName", "bjensen"); // - added target.put("nickName", "bjj3"); // - removed source.put("title", "hot shot"); target.putNull("title"); // - updated source.put("userType", "employee"); target.put("userType", "manager"); // - non-asserted source.put("displayName", "don't touch"); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 2); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("title")))); ObjectNode replaceValue = JsonUtils.getJsonNodeFactory().objectNode(); replaceValue.put("userType", "manager"); replaceValue.put("nickName", "bjj3"); assertTrue(d.contains(PatchOperation.replace(replaceValue))); List<PatchOperation> d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); } /** * Test comparison of single-valued complex attributes. * * @throws Exception if an error occurs. */ @Test public void testDiffSingularComplexAttribute() throws Exception { // *** singular complex *** // - unchanged ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode name = JsonUtils.valueToNode( new Name().setFormatted("Ms. Barbara J Jensen III").setFamilyName("Jensen").setMiddleName("J") .setGivenName("Barbara").setHonorificPrefix("Ms.").setHonorificSuffix("III")); source.set("name", name); target.set("name", name); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 0); List<PatchOperation> d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); // - added source = JsonUtils.getJsonNodeFactory().objectNode(); target = JsonUtils.getJsonNodeFactory().objectNode(); target.set("name", name); d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 1); assertTrue(d.contains(PatchOperation .replace((ObjectNode) JsonUtils.getJsonNodeFactory().objectNode().set("name", name)))); d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); // - removed source = JsonUtils.getJsonNodeFactory().objectNode(); target = JsonUtils.getJsonNodeFactory().objectNode(); source.set("name", name); target.putNull("name"); d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 1); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("name")))); d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); // - removed a sub-attribute source = JsonUtils.getJsonNodeFactory().objectNode(); target = JsonUtils.getJsonNodeFactory().objectNode(); source.set("name", name); target.set("name", name.deepCopy().putNull("honorificSuffix")); d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 1); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("name").attribute("honorificSuffix")))); d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); // - updated source = JsonUtils.getJsonNodeFactory().objectNode(); target = JsonUtils.getJsonNodeFactory().objectNode(); source.set("name", name); target.set("name", name.deepCopy().put("familyName", "Johnson")); d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 1); ObjectNode replaceValue = JsonUtils.getJsonNodeFactory().objectNode(); replaceValue.putObject("name").put("familyName", "Johnson"); assertTrue(d.contains(PatchOperation.replace(replaceValue))); d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); // - non-asserted source = JsonUtils.getJsonNodeFactory().objectNode(); target = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode nameCopy = name.deepCopy(); nameCopy.remove("middleName"); source.set("name", name); target.set("name", nameCopy); d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 0); d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); } /** * Test comparison of multi-valued complex attributes. * * @throws Exception if an error occurs. */ @Test public void testDiffMultiValuedAttribute() throws Exception { // *** multi-valued *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); // - unchanged String email1 = "bjensen@example.com"; String email2 = "babs@jensen.org"; source.putArray("emails").add(email1).add(email2); target.putArray("emails").add(email1).add(email2); // - added String phone1 = "1234567890"; String phone2 = "0987654321"; target.putArray("phones").add(phone1).add(phone2); // - removed String im1 = "babs"; String im2 = "bjensen"; source.putArray("ims").add(im1).add(im2); target.putArray("ims"); // - updated // -- unchanged String photo0 = "http://photo0"; String photo1 = "http://photo1"; // -- add a new value String photo3 = "http://photo3"; // -- remove a value String thumbnail = "http://thumbnail1"; source.putArray("photos").add(photo0).add(photo1).add(thumbnail); target.putArray("photos").add(photo0).add(photo1).add(photo3); // -- updated with all new values String entitlement1 = "admin"; String entitlement2 = "user"; String entitlement3 = "inactive"; source.putArray("entitlements").add(entitlement1).add(entitlement2); target.putArray("entitlements").add(entitlement3); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 4); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("ims")))); assertTrue( d.contains(PatchOperation.remove(Path.root().attribute("photos", Filter.eq("value", thumbnail))))); ObjectNode replaceValue = JsonUtils.getJsonNodeFactory().objectNode(); replaceValue.putArray("entitlements").add(entitlement3); replaceValue.putArray("phones").add(phone1).add(phone2); assertTrue(d.contains(PatchOperation.replace(replaceValue))); ObjectNode addValue = JsonUtils.getJsonNodeFactory().objectNode(); addValue.putArray("photos").add(photo3); assertTrue(d.contains(PatchOperation.add(addValue))); List<PatchOperation> d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); // Have to compare photos explicitly since ordering of array values doesn't // matter. JsonNode sourcePhotos = source.remove("photos"); JsonNode targetPhotos = target.remove("photos"); assertEquals(sourcePhotos.size(), targetPhotos.size()); for (JsonNode sourceValue : sourcePhotos) { boolean found = false; for (JsonNode targetValue : targetPhotos) { if (sourceValue.equals(targetValue)) { found = true; break; } } if (!found) { fail("Source photo value " + sourceValue + " not in target photo array " + targetPhotos); } } assertEquals(source, target); } /** * Test comparison of multi-valued complex attributes. * * @throws Exception if an error occurs. */ @Test public void testDiffMultiValuedComplexAttribute() throws Exception { // *** multi-valued *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); // - unchanged ObjectNode email1 = JsonUtils .valueToNode(new Email().setValue("bjensen@example.com").setType("work").setPrimary(true)); ObjectNode email2 = JsonUtils .valueToNode(new Email().setValue("babs@jensen.org").setType("home").setPrimary(false)); source.putArray("emails").add(email1).add(email2); target.putArray("emails").add(email1).add(email2); // - added ObjectNode phone1 = JsonUtils .valueToNode(new PhoneNumber().setValue("1234567890").setType("work").setPrimary(true)); ObjectNode phone2 = JsonUtils .valueToNode(new PhoneNumber().setValue("0987654321").setType("home").setPrimary(false)); target.putArray("phones").add(phone1).add(phone2); // - removed ObjectNode im1 = JsonUtils .valueToNode(new InstantMessagingAddress().setValue("babs").setType("aim").setPrimary(true)); ObjectNode im2 = JsonUtils .valueToNode(new InstantMessagingAddress().setValue("bjensen").setType("gtalk").setPrimary(false)); source.putArray("ims").add(im1).add(im2); target.putArray("ims"); // - updated // -- unchanged ObjectNode photo0 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo0")).setType("photo0").setPrimary(false)); ObjectNode photo1 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo1")).setType("photo1").setPrimary(false)); // -- non-asserted ObjectNode photo2 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo2")).setType("photo2").setPrimary(false)); ObjectNode photo2a = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo2")).setType("photo2")); // -- add a new value ObjectNode photo3 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo3")).setType("photo3").setPrimary(true)); // -- update an existing value ObjectNode photo4 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo4")).setType("photo4").setPrimary(true)); ObjectNode photo4a = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo4")).setType("photo4").setPrimary(false)); // -- add a new value ObjectNode photo5 = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo5")).setType("photo5").setPrimary(false)); ObjectNode photo5a = JsonUtils.valueToNode(new Photo().setValue(new URI("http://photo5")).setType("photo5") .setPrimary(false).setDisplay("Photo 5")); // -- remove an existing value ObjectNode photo6 = JsonUtils.valueToNode(new Photo().setValue(new URI("http://photo6")).setType("photo6") .setPrimary(false).setDisplay("Photo 6")); ObjectNode photo6a = JsonUtils .valueToNode(new Photo().setValue(new URI("http://photo6")).setType("photo6").setPrimary(false)); photo6a.putNull("display"); // -- remove a value ObjectNode thumbnail = JsonUtils.valueToNode( new Photo().setValue(new URI("http://thumbnail1")).setType("thumbnail").setPrimary(true)); source.putArray("photos").add(photo0).add(photo1).add(photo2).add(photo4).add(photo5).add(photo6) .add(thumbnail); target.putArray("photos").add(photo0).add(photo1).add(photo2a).add(photo4a).add(photo5a).add(photo6a) .add(photo3); // -- updated with all new values ObjectNode entitlement1 = JsonUtils.valueToNode(new Entitlement().setValue("admin").setPrimary(false)); ObjectNode entitlement2 = JsonUtils.valueToNode(new Entitlement().setValue("user").setPrimary(false)); ObjectNode entitlement3 = JsonUtils.valueToNode(new Entitlement().setValue("inactive").setPrimary(true)); source.putArray("entitlements").add(entitlement1).add(entitlement2); target.putArray("entitlements").add(entitlement3); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 7); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("ims")))); assertTrue(d.contains(PatchOperation.remove(Path.root() .attribute("photos", Filter.fromString("value eq \"http://photo6\" and " + "display eq \"Photo 6\" and " + "type eq \"photo6\" and " + "primary eq false")) .attribute("display")))); assertTrue( d.contains(PatchOperation.replace( Path.root().attribute("photos", Filter.fromString("value eq \"http://photo4\" and " + "type eq \"photo4\" and " + "primary eq true")), JsonUtils.getJsonNodeFactory().objectNode().put("primary", false)))); assertTrue(d.contains(PatchOperation.replace( Path.root().attribute("photos", Filter.fromString("value eq \"http://photo5\" and " + "type eq \"photo5\" and " + "primary eq false")), JsonUtils.getJsonNodeFactory().objectNode().put("display", "Photo 5")))); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("photos", Filter.fromString( "value eq \"http://thumbnail1\" and " + "type eq \"thumbnail\" and " + "primary eq true"))))); ObjectNode replaceValue = JsonUtils.getJsonNodeFactory().objectNode(); replaceValue.putArray("entitlements").add(entitlement3); replaceValue.putArray("phones").add(phone1).add(phone2); assertTrue(d.contains(PatchOperation.replace(replaceValue))); ObjectNode addValue = JsonUtils.getJsonNodeFactory().objectNode(); addValue.putArray("photos").add(photo3); assertTrue(d.contains(PatchOperation.add(addValue))); List<PatchOperation> d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); } /** * Test comparison against 1st {@code null} object. * * @throws Exception * if an error occurs. */ @Test public void testDiffNullObject1() throws Exception { // *** singular *** ObjectNode source = null; ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); // - unchanged target.put("userName", "bjensen"); target.put("nickName", "bjj3"); target.put("title", "hot shot"); target.put("userType", "employee"); try { List<PatchOperation> d = JsonUtils.diff(source, target, false); fail("Expected NullPointerException"); } catch (NullPointerException e) { // pass } } /** * Test comparison against 2nd {@code null} object. * * @throws Exception * if an error occurs. */ @Test public void testDiffNullObject2() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = null; // - unchanged source.put("userName", "bjensen"); source.put("nickName", "bjj3"); source.put("title", "hot shot"); source.put("userType", "employee"); try { List<PatchOperation> d = JsonUtils.diff(source, target, false); fail("Expected NullPointerException"); } catch (NullPointerException e) { // pass } } /** * Test comparison of {@code null} objects. * * @throws Exception * if an error occurs. */ @Test public void testDiffNullObjects() throws Exception { // *** singular *** ObjectNode source = null; ObjectNode target = null; try { List<PatchOperation> d = JsonUtils.diff(source, target, false); fail("Expected NullPointerException"); } catch (NullPointerException e) { // pass } try { List<PatchOperation> d = JsonUtils.diff(source, target, true); fail("Expected NullPointerException"); } catch (NullPointerException e) { // pass } } /** * Test comparison of same object with itself. * * @throws Exception * if an error occurs. */ @Test public void testDiffSameObject() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = source; // - unchanged source.put("userName", "bjensen"); source.put("nickName", "bjj3"); source.put("title", "hot shot"); source.put("userType", "employee"); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 0); } /** * Test comparison of empty objects. * * @throws Exception * if an error occurs. */ @Test public void testDiffEmptyObjects() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 0); } /** * Test comparison of objects with equal attributes. * * @throws Exception * if an error occurs. */ @Test public void testDiffNoChanges() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); source.put("userName", "bjensen"); target.put("userName", "bjensen"); source.put("nickName", "bjj3"); target.put("nickName", "bjj3"); source.put("title", "hot shot"); target.put("title", "hot shot"); source.put("userType", 1); target.put("userType", new BigDecimal(1)); source.put("password", "cGFzc3dvcmQ="); target.put("password", "password".getBytes()); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 0); } /** * Test comparison of objects removing all attributes. * * @throws Exception * if an error occurs. */ @Test public void testRemoveAll() throws Exception { // *** singular *** ObjectNode source = JsonUtils.getJsonNodeFactory().objectNode(); ObjectNode target = JsonUtils.getJsonNodeFactory().objectNode(); // - unchanged source.put("userName", "bjensen"); target.putNull("userName"); source.put("nickName", "bjj3"); target.putNull("nickName"); source.put("title", "hot shot"); target.putNull("title"); source.put("userType", "employee"); target.putArray("userType"); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 4); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("userName")))); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("nickName")))); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("title")))); assertTrue(d.contains(PatchOperation.remove(Path.root().attribute("userType")))); target = JsonUtils.getJsonNodeFactory().objectNode(); List<PatchOperation> d2 = JsonUtils.diff(source, target, true); for (PatchOperation op : d2) { op.apply(source); } removeNullNodes(target); assertEquals(source, target); } /** * Sanity test with source and target objects containing multiple attribute * types but with only a small difference. * * @throws Exception * if an error occurs. */ @Test public void sanityTest() throws Exception { ObjectNode source = (ObjectNode) JsonUtils.getObjectReader().readTree("{\n" + " \"addresses\": [\n" + " {\n" + " \"streetAddress\": \"60804 Ridge Street\",\n" + " \"locality\": \"Indianapolis\",\n" + " \"region\": \"HI\",\n" + " \"postalCode\": \"92756\",\n" + " \"country\": \"US\",\n" + " \"primary\": true,\n" + " \"type\": \"home\"\n" + " }\n" + " ],\n" + " \"someObject\": [\n" + " {\n" + " \"someField\": \"A\"\n" + " }\n" + " ],\n" + " \"urn:pingidentity:schemas:sample:profile:1.0\": {\n" + " \"birthdayDayMonth\": {\n" + " \"day\": 24,\n" + " \"month\": 12\n" + " },\n" + " \"communicationOpts\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:SMSMarketing\",\n" + " \"destination\": \"+1 921 433 6722\",\n" + " \"destinationType\": \"sms\",\n" + " \"polarityOpt\": \"out\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"frequency\": \"daily;5:33\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:EmailMarketing\",\n" + " \"destination\": \"user.13@example.com\",\n" + " \"destinationType\": \"email\",\n" + " \"polarityOpt\": \"in\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"frequency\": \"daily;5:33\"\n" + " }\n" + " ],\n" + " \"contentOpts\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Coupons\",\n" + " \"polarityOpt\": \"out\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Newsletters\",\n" + " \"polarityOpt\": \"in\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Notification\",\n" + " \"polarityOpt\": \"in\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\"\n" + " }\n" + " ],\n" + " \"postalCode\": \"92756\",\n" + " \"termsOfService\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:ToS:StandardUser:1.0\",\n" + " \"timeStamp\": \"2013-11-13T12:40:57Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Mobile:1.0\"\n" + " }\n" + " ],\n" + " \"topicPreferences\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:finance:renting\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2014-12-20T17:54:25Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:auto:maintenance\",\n" + " \"strength\": -8,\n" + " \"timeStamp\": \"2013-11-25T12:45:21Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:health:heart\",\n" + " \"strength\": -5,\n" + " \"timeStamp\": \"2013-10-30T13:32:39Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:shoes\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:workout\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:casual\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:accessories\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:impress\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " }\n" + " ]\n" + " },\n" + " \"displayName\": \"Jacquelynn Ellis\",\n" + " \"emails\": [\n" + " {\n" + " \"value\": \"user.13@example.com\",\n" + " \"verified\": false,\n" + " \"primary\": false,\n" + " \"type\": \"work\"\n" + " },\n" + " {\n" + " \"value\": \"Jacquelynn.Ellis@gmail.com\",\n" + " \"verified\": true,\n" + " \"primary\": true,\n" + " \"type\": \"home\"\n" + " }\n" + " ],\n" + " \"meta\": {\n" + " \"lastModified\": \"2015-10-13T16:54:59.157Z\",\n" + " \"resourceType\": \"Users\"\n" + " },\n" + " \"name\": {\n" + " \"familyName\": \"Ellis\",\n" + " \"formatted\": \"Chad Sanford\",\n" + " \"givenName\": \"Jacquelynn\",\n" + " \"middleName\": \"Krystle\"\n" + " },\n" + " \"phoneNumbers\": [\n" + " {\n" + " \"value\": \"+1 909 234 2568\",\n" + " \"type\": \"work\",\n" + " \"verified\": false,\n" + " \"primary\": false\n" + " },\n" + " {\n" + " \"value\": \"+1 191 623 7660\",\n" + " \"type\": \"home\",\n" + " \"verified\": false,\n" + " \"primary\": false\n" + " },\n" + " {\n" + " \"value\": \"+1 490 020 8366\",\n" + " \"type\": \"mobile\",\n" + " \"verified\": true,\n" + " \"primary\": true\n" + " }\n" + " ],\n" + " \"userName\": \"user.0\",\n" + " \"id\": \"ad55a34a-763f-358f-93f9-da86f9ecd9e4\",\n" + " \"schemas\": [\n" + " \"urn:pingidentity:schemas:User:1.0\",\n" + " \"urn:pingidentity:schemas:sample:profile:1.0\"\n" + " ]\n" + "}"); ObjectNode target = (ObjectNode) JsonUtils.getObjectReader().readTree("{\n" + " \"schemas\": [\n" + " \"urn:pingidentity:schemas:User:1.0\",\n" + " \"urn:pingidentity:schemas:sample:profile:1.0\"\n" + " ],\n" + " \"addresses\": [\n" + " {\n" + " \"streetAddress\": \"60804 Ridge Street\",\n" + " \"locality\": \"Indianapolis\",\n" + " \"region\": \"HI\",\n" + " \"postalCode\": \"92756\",\n" + " \"country\": \"US\",\n" + " \"primary\": true,\n" + " \"type\": \"home\"\n" + " }\n" + " ],\n" + " \"someObject\": [\n" + " {\n" + " \"someField\": \"B\"\n" + " }\n" + " ],\n" + " \"displayName\": \"Jacquelynn Ellis\",\n" + " \"emails\": [\n" + " {\n" + " \"value\": \"user.13@example.com\",\n" + " \"verified\": false,\n" + " \"primary\": false,\n" + " \"type\": \"work\"\n" + " },\n" + " {\n" + " \"value\": \"Jacquelynn.Ellis@gmail.com\",\n" + " \"verified\": true,\n" + " \"primary\": true,\n" + " \"type\": \"home\"\n" + " }\n" + " ],\n" + " \"name\": {\n" + " \"familyName\": \"Ellis\",\n" + " \"formatted\": \"Chad Sanford\",\n" + " \"givenName\": \"Jacquelynn\",\n" + " \"middleName\": \"Krystle\"\n" + " },\n" + " \"phoneNumbers\": [\n" + " {\n" + " \"value\": \"+1 909 234 2568\",\n" + " \"type\": \"work\",\n" + " \"verified\": false,\n" + " \"primary\": false\n" + " },\n" + " {\n" + " \"value\": \"+1 191 623 7660\",\n" + " \"type\": \"home\",\n" + " \"verified\": false,\n" + " \"primary\": false\n" + " },\n" + " {\n" + " \"value\": \"+1 490 020 8366\",\n" + " \"type\": \"mobile\",\n" + " \"verified\": true,\n" + " \"primary\": true\n" + " }\n" + " ],\n" + " \"urn:pingidentity:schemas:sample:profile:1.0\": {\n" + " \"birthdayDayMonth\": {\n" + " \"day\": 24,\n" + " \"month\": 12\n" + " },\n" + " \"communicationOpts\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:SMSMarketing\",\n" + " \"destination\": \"+1 921 433 6722\",\n" + " \"destinationType\": \"sms\",\n" + " \"polarityOpt\": \"out\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"frequency\": \"daily;5:33\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:EmailMarketing\",\n" + " \"destination\": \"user.13@example.com\",\n" + " \"destinationType\": \"email\",\n" + " \"polarityOpt\": \"in\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"frequency\": \"daily;5:33\"\n" + " }\n" + " ],\n" + " \"contentOpts\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Coupons\",\n" + " \"polarityOpt\": \"out\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Newsletters\",\n" + " \"polarityOpt\": \"in\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:Opt:Notification\",\n" + " \"polarityOpt\": \"in\",\n" + " \"collector\": \"urn:X-UnboundID:App:Profile-Manager:1.0\",\n" + " \"timeStamp\": \"2015-10-11T22:58:08.608Z\"\n" + " }\n" + " ],\n" + " \"postalCode\": \"92756\",\n" + " \"termsOfService\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:ToS:StandardUser:1.0\",\n" + " \"timeStamp\": \"2013-11-13T12:40:57Z\",\n" + " \"collector\": \"urn:X-UnboundID:App:Mobile:1.0\"\n" + " }\n" + " ],\n" + " \"topicPreferences\": [\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:finance:renting\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2014-12-20T17:54:25Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:auto:maintenance\",\n" + " \"strength\": -8,\n" + " \"timeStamp\": \"2013-11-25T12:45:21Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:health:heart\",\n" + " \"strength\": -5,\n" + " \"timeStamp\": \"2013-10-30T13:32:39Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:shoes\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-13T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:workout\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:casual\",\n" + " \"strength\": 10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:accessories\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " },\n" + " {\n" + " \"id\": \"urn:X-UnboundID:topic:clothing:impress\",\n" + " \"strength\": -10,\n" + " \"timeStamp\": \"2015-10-12T14:57:36.494Z\"\n" + " }\n" + " ]\n" + " },\n" + " \"userName\": \"user.0\",\n" + " \"photoURLs\": [\n" + " \n" + " ]\n" + "}"); List<PatchOperation> d = JsonUtils.diff(source, target, false); assertEquals(d.size(), 2); assertEquals(d.get(0).getOpType(), PatchOpType.REPLACE); assertEquals(d.get(0).getPath().toString(), "urn:pingidentity:schemas:sample:profile:1.0:topicPreferences[" + "(id eq \"urn:X-UnboundID:topic:clothing:shoes\" and" + " strength eq 10 and timeStamp eq \"2015-10-12T14:57:36.494Z\")]"); assertEquals(d.get(0).getJsonNode().path("timeStamp").textValue(), "2015-10-13T14:57:36.494Z"); } /** * Test the case of creating a patch for an object with multivalued * attributes that have no values, and supplying the same object to * diff with. We should return no patch operations, however in previous * iterations of this code, we would try and remove the attribute. * * @throws IOException if an error occurs */ @Test public void testMultiValuedAttributesWithEmptyValues() throws IOException { String jsonString = "{\n" + " \"schemas\" : [ \"urn:unboundid:configuration:2.0\" ],\n" + " \"id\" : \"userRoot2\",\n" + " \"meta\" : {\n" + " \"resourceType\" : \"LocalDbBackend\",\n" + " \"location\" : \"http://localhost:5033/config/v2/Backends/userRoot2\"\n" + " },\n" + " \"urn:unboundid:configuration:2.0\" : {\n" + " \"type\" : \"LocalDbBackend\"\n" + " },\n" + " \"backendId\" : \"userRoot2\",\n" + " \"backgroundPrime\" : \"false\",\n" + " \"backupFilePermissions\" : \"700\",\n" + " \"baseDn\" : [ \"dc=example2,dc=com\" ],\n" + " \"checkpointOnCloseCount\" : \"2\",\n" + " \"cleanerThreadWaitTime\" : \"120000\",\n" + " \"compactCommonParentDn\" : [ ],\n" + " \"jeProperty\" : [ ]\n" + "}"; ObjectNode source = JsonUtils.getObjectReader().forType(ObjectNode.class).readValue(jsonString); ObjectNode target = JsonUtils.getObjectReader().forType(ObjectNode.class).readValue(jsonString); List<PatchOperation> d = JsonUtils.diff(source, target, false); Assert.assertEquals(d.size(), 0); } private void removeNullNodes(JsonNode object) { Iterator<JsonNode> i = object.elements(); while (i.hasNext()) { JsonNode field = i.next(); if (field.isNull() || (field.isArray() && field.size() == 0)) { i.remove(); } else { removeNullNodes(field); } } } }