You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2016/12/14 10:57:09 UTC

[3/4] camel git commit: CAMEL-10571 SObject tree creation, Composite API

http://git-wip-us.apache.org/repos/asf/camel/blob/e1cfeb5a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/CompositeTestBase.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/CompositeTestBase.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/CompositeTestBase.java
new file mode 100644
index 0000000..d2e3e4e
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/CompositeTestBase.java
@@ -0,0 +1,66 @@
+/**
+ * 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.camel.component.salesforce.api.dto.composite;
+
+import org.apache.camel.component.salesforce.dto.generated.Account;
+import org.apache.camel.component.salesforce.dto.generated.Account_IndustryEnum;
+import org.apache.camel.component.salesforce.dto.generated.Contact;
+
+abstract class CompositeTestBase {
+
+    final Account simpleAccount = new Account();
+
+    final Contact smith = new Contact();
+
+    final Contact evans = new Contact();
+
+    final Contact bond = new Contact();
+
+    final Contact moneypenny = new Contact();
+
+    final Account simpleAccount2 = new Account();
+
+    CompositeTestBase() {
+        simpleAccount.setName("SampleAccount");
+        simpleAccount.setPhone("1234567890");
+        simpleAccount.setWebsite("www.salesforce.com");
+        simpleAccount.setNumberOfEmployees(100);
+        simpleAccount.setIndustry(Account_IndustryEnum.BANKING);
+
+        smith.setLastName("Smith");
+        smith.setTitle("President");
+        smith.setEmail("sample@salesforce.com");
+
+        evans.setLastName("Evans");
+        evans.setTitle("Vice President");
+        evans.setEmail("sample@salesforce.com");
+
+        bond.setLastName("Bond");
+        bond.setTitle("Agent to the crown");
+        bond.setEmail("sample@salesforce.com");
+
+        moneypenny.setLastName("Moneypenny");
+        moneypenny.setTitle("Secretary");
+        moneypenny.setEmail("sample@salesforce.com");
+
+        simpleAccount2.setName("SampleAccount2");
+        simpleAccount2.setPhone("1234567890");
+        simpleAccount2.setWebsite("www.salesforce2.com");
+        simpleAccount2.setNumberOfEmployees(100);
+        simpleAccount2.setIndustry(Account_IndustryEnum.BANKING);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e1cfeb5a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectNodeTest.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectNodeTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectNodeTest.java
new file mode 100644
index 0000000..86e801a
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectNodeTest.java
@@ -0,0 +1,199 @@
+/**
+ * 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.camel.component.salesforce.api.dto.composite;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
+import org.apache.camel.component.salesforce.dto.generated.Account;
+import org.apache.camel.component.salesforce.dto.generated.Contact;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class SObjectNodeTest extends CompositeTestBase {
+
+    static SObjectNode[] toArray(final Stream<SObjectNode> children) {
+        return children.toArray(l -> new SObjectNode[l]);
+    }
+
+    @Test
+    public void shouldBeAbleToAddChildNode() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", new SObjectNode(tree, smith));
+
+        final Stream<SObjectNode> children = node.getChildNodesOfType("Contacts");
+        final SObjectNode[] childrenAry = toArray(children);
+
+        assertEquals("Size of the node should be 2", 2, node.size());
+
+        assertEquals("There should be one child in this node", 1, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0].getObject());
+    }
+
+    @Test
+    public void shouldBeAbleToAddChildObject() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", smith);
+
+        final Stream<SObjectNode> children = node.getChildNodesOfType("Contacts");
+        final SObjectNode[] childrenAry = toArray(children);
+
+        assertEquals("Size of the node should be 2", 2, node.size());
+
+        assertEquals("There should be one child in this node", 1, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0].getObject());
+    }
+
+    @Test
+    public void shouldBeAbleToFetchChildNodes() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", new SObjectNode(tree, smith));
+        node.addChild("Contacts", new SObjectNode(tree, evans));
+
+        final Stream<SObjectNode> children = node.getChildNodes();
+        final SObjectNode[] childrenAry = toArray(children);
+
+        assertEquals("There should be two child records in this node", 2, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0].getObject());
+        assertSame("Second record should be evans contact", evans, childrenAry[1].getObject());
+
+        assertEquals("Size of the node should be 3", 3, node.size());
+    }
+
+    @Test
+    public void shouldBeAbleToFetchChildren() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", smith);
+        node.addChild("Contacts", evans);
+
+        final Stream<AbstractSObjectBase> children = node.getChildren();
+        final Object[] childrenAry = children.toArray();
+
+        assertEquals("There should be two child records in this node", 2, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0]);
+        assertSame("Second record should be evans contact", evans, childrenAry[1]);
+
+        assertEquals("Size of the node should be 3", 3, node.size());
+    }
+
+    @Test
+    public void shouldCreateNode() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", new SObjectNode(tree, smith));
+        node.addChild("Contacts", new SObjectNode(tree, evans));
+
+        assertSame("Object in the node should be the given account", simpleAccount, node.getObject());
+        assertEquals("Type of the object in node should be auto-detected", "Account", node.getObjectType());
+
+        final Stream<SObjectNode> children = node.getChildNodesOfType("Contacts");
+        final SObjectNode[] childrenAry = toArray(children);
+
+        assertEquals("There should be two records in this node", 2, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0].getObject());
+        assertEquals("Type of first record should be Contact", "Contact", childrenAry[0].getObjectType());
+
+        assertSame("Second record should be evans contact", evans, childrenAry[1].getObject());
+        assertEquals("Type of second record should be Contact", "Contact", childrenAry[1].getObjectType());
+
+        assertEquals("Size of the node should be 3", 3, node.size());
+    }
+
+    @Test
+    public void shouldCreateNodeWithoutChildRecords() {
+        new SObjectNode(new SObjectTree(), simpleAccount);
+    }
+
+    @Test
+    public void shouldFetchChildrenNodesOfType() {
+        final SObjectTree tree = new SObjectTree();
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", new SObjectNode(tree, smith));
+        node.addChild("Contacts", new SObjectNode(tree, evans));
+
+        final Stream<SObjectNode> children = node.getChildNodesOfType("Contacts");
+        final SObjectNode[] childrenAry = toArray(children);
+
+        assertEquals("There should be two records in this node", 2, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0].getObject());
+        assertSame("Second record should be evans contact", evans, childrenAry[1].getObject());
+
+        assertEquals("Size of the node should be 3", 3, node.size());
+    }
+
+    @Test
+    public void shouldFetchChildrenOfType() {
+        final SObjectTree tree = new SObjectTree();
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild("Contacts", smith);
+        node.addChild("Contacts", evans);
+
+        final Stream<AbstractSObjectBase> children = node.getChildrenOfType("Contacts");
+        final Object[] childrenAry = children.toArray();
+
+        assertEquals("There should be two child records in this node", 2, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0]);
+        assertSame("Second record should be evans contact", evans, childrenAry[1]);
+
+        assertEquals("Size of the node should be 3", 3, node.size());
+    }
+
+    @Test
+    public void shouldSupportAddingDescribedSObjects() {
+        final SObjectTree tree = new SObjectTree();
+        final SObjectNode node = new SObjectNode(tree, simpleAccount);
+        node.addChild(smith);
+        node.addChildren(evans);
+        node.addChildren(bond, moneypenny);
+
+        final Stream<AbstractSObjectBase> children = node.getChildrenOfType("Contacts");
+        final Object[] childrenAry = children.toArray();
+
+        assertEquals("There should be four records in this node", 4, childrenAry.length);
+
+        assertSame("First record should be smith contact", smith, childrenAry[0]);
+        assertSame("Second record should be evans contact", evans, childrenAry[1]);
+        assertSame("Third record should be bond contact", bond, childrenAry[2]);
+        assertSame("Fourth record should be moneypeny contact", moneypenny, childrenAry[3]);
+
+        assertEquals("Size of the node should be 5", 5, node.size());
+    }
+
+    @Test
+    public void typeOfShouldBeBasedOnSimpleClassName() {
+        assertEquals("Type of Account should be 'Account'", "Account", SObjectNode.typeOf(new Account()));
+        assertEquals("Type of Contact should be 'Contact'", "Contact", SObjectNode.typeOf(new Contact()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e1cfeb5a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeResponseTest.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeResponseTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeResponseTest.java
new file mode 100644
index 0000000..ebdfe5d
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeResponseTest.java
@@ -0,0 +1,171 @@
+/**
+ * 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.camel.component.salesforce.api.dto.composite;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.thoughtworks.xstream.XStream;
+
+import org.apache.camel.component.salesforce.api.dto.RestError;
+import org.junit.Test;
+
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class SObjectTreeResponseTest {
+
+    @Test
+    public void shouldDeserializeJsonFromSalesforceExample() throws Exception {
+        final String json = "{\n"//
+            + "    \"hasErrors\" : false,\n"//
+            + "    \"results\" : [{\n"//
+            + "     \"referenceId\" : \"ref1\",\n"//
+            + "     \"id\" : \"001D000000K0fXOIAZ\"\n"//
+            + "     },{\n"//
+            + "     \"referenceId\" : \"ref4\",\n"//
+            + "     \"id\" : \"001D000000K0fXPIAZ\"\n"//
+            + "     },{\n"//
+            + "     \"referenceId\" : \"ref2\",\n"//
+            + "     \"id\" : \"003D000000QV9n2IAD\"\n"//
+            + "     },{\n"//
+            + "     \"referenceId\" : \"ref3\",\n"//
+            + "     \"id\" : \"003D000000QV9n3IAD\"\n"//
+            + "     }]\n"//
+            + "}";
+
+        final ObjectMapper mapper = new ObjectMapper();
+
+        final ObjectReader reader = mapper.readerFor(SObjectTreeResponse.class);
+        final SObjectTreeResponse response = reader.readValue(json);
+
+        assertNotNull("Response should be parsed", response);
+
+        assertFalse("`hasErrors` flag should be false", response.hasErrors());
+
+        assertEquals("Should read 4 references", 4, response.getResults().size());
+        assertThat("4 references should be read as expected", response.getResults(),
+            hasItems(new ReferenceId("ref1", "001D000000K0fXOIAZ", Collections.emptyList()), //
+                new ReferenceId("ref4", "001D000000K0fXPIAZ", Collections.emptyList()), //
+                new ReferenceId("ref2", "003D000000QV9n2IAD", Collections.emptyList()), //
+                new ReferenceId("ref3", "003D000000QV9n3IAD", Collections.emptyList())));
+    }
+
+    @Test
+    public void shouldDeserializeJsonFromSalesforceFailureExample() throws Exception {
+        final String json = "{\n"//
+            + "   \"hasErrors\" : true,\n"//
+            + "   \"results\" : [{\n"//
+            + "     \"referenceId\" : \"ref2\",\n"//
+            + "     \"errors\" : [{\n"//
+            + "       \"statusCode\" : \"INVALID_EMAIL_ADDRESS\",\n"//
+            + "       \"message\" : \"Email: invalid email address: 123\",\n"//
+            + "       \"fields\" : [ \"Email\" ]\n"//
+            + "       }]\n"//
+            + "     }]\n"//
+            + "}";
+
+        final ObjectMapper mapper = new ObjectMapper();
+
+        final ObjectReader reader = mapper.readerFor(SObjectTreeResponse.class);
+        final SObjectTreeResponse response = reader.readValue(json);
+
+        assertNotNull("Response should be parsed", response);
+
+        assertTrue("`hasErrors` flag should be true", response.hasErrors());
+
+        assertEquals("Should read one reference", 1, response.getResults().size());
+        assertThat("The reference should be read as expected", response.getResults(),
+            hasItems(new ReferenceId("ref2", null, Arrays.asList(
+                new RestError("INVALID_EMAIL_ADDRESS", "Email: invalid email address: 123", Arrays.asList("Email"))))));
+    }
+
+    @Test
+    public void shouldDeserializeXmlFromSalesforceExample() throws Exception {
+        final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"//
+            + "<Result>\n"//
+            + "    <hasErrors>false</hasErrors>\n"//
+            + "    <results>\n"//
+            + "        <id>001D000000K0fXOIAZ</id>\n"//
+            + "        <referenceId>ref1</referenceId>\n"//
+            + "    </results>\n"//
+            + "    <results>\n"//
+            + "        <id>001D000000K0fXPIAZ</id>\n"//
+            + "        <referenceId>ref4</referenceId>\n"//
+            + "    </results>\n"//
+            + "    <results>\n"//
+            + "        <id>003D000000QV9n2IAD</id>\n"//
+            + "        <referenceId>ref2</referenceId>\n"//
+            + "    </results>\n"//
+            + "    <results>\n"//
+            + "        <id>003D000000QV9n3IAD</id>\n"//
+            + "        <referenceId>ref3</referenceId>\n"//
+            + "    </results>\n"//
+            + "</Result>";
+
+        final XStream xStream = new XStream();
+        xStream.processAnnotations(new Class[] {SObjectTreeResponse.class});
+
+        final SObjectTreeResponse response = (SObjectTreeResponse) xStream.fromXML(xml);
+
+        assertNotNull("Response should be parsed", response);
+
+        assertFalse("`hasErrors` flag should be false", response.hasErrors());
+
+        assertEquals("Should read 4 references", 4, response.getResults().size());
+        assertThat("4 references should be read as expected", response.getResults(),
+            hasItems(new ReferenceId("ref1", "001D000000K0fXOIAZ", Collections.emptyList()), //
+                new ReferenceId("ref4", "001D000000K0fXPIAZ", Collections.emptyList()), //
+                new ReferenceId("ref2", "003D000000QV9n2IAD", Collections.emptyList()), //
+                new ReferenceId("ref3", "003D000000QV9n3IAD", Collections.emptyList())));
+    }
+
+    @Test
+    public void shouldDeserializeXmlFromSalesforceFailureExample() throws Exception {
+        final String xml = "<Result>\n"//
+            + "    <hasErrors>true</hasErrors>\n"//
+            + "    <results>\n"//
+            + "        <errors>\n"//
+            + "            <fields>Email</fields>\n"//
+            + "            <message>Email: invalid email address: 123</message>\n"//
+            + "            <statusCode>INVALID_EMAIL_ADDRESS</statusCode>\n"//
+            + "        </errors>\n"//
+            + "        <referenceId>ref2</referenceId>\n"//
+            + "    </results>\n"//
+            + "</Result>";
+
+        final XStream xStream = new XStream();
+        xStream.processAnnotations(new Class[] {SObjectTreeResponse.class});
+
+        final SObjectTreeResponse response = (SObjectTreeResponse) xStream.fromXML(xml);
+
+        assertNotNull("Response should be parsed", response);
+
+        assertTrue("`hasErrors` flag should be true", response.hasErrors());
+
+        assertEquals("Should read one reference", 1, response.getResults().size());
+        assertThat("The reference should be read as expected", response.getResults(),
+            hasItems(new ReferenceId("ref2", null, Arrays.asList(
+                new RestError("INVALID_EMAIL_ADDRESS", "Email: invalid email address: 123", Arrays.asList("Email"))))));
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e1cfeb5a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeTest.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeTest.java
new file mode 100644
index 0000000..4f0e46f
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/api/dto/composite/SObjectTreeTest.java
@@ -0,0 +1,291 @@
+/**
+ * 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.camel.component.salesforce.api.dto.composite;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.thoughtworks.xstream.XStream;
+
+import org.apache.camel.component.salesforce.dto.generated.Account;
+import org.apache.camel.component.salesforce.dto.generated.Asset;
+import org.apache.camel.component.salesforce.dto.generated.Contact;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class SObjectTreeTest extends CompositeTestBase {
+
+    @Test
+    public void emptyTreeShouldBeZeroSized() {
+        assertEquals(0, new SObjectTree().size());
+    }
+
+    @Test
+    public void shouldCollectAllObjectTypesInTheTree() {
+        final SObjectTree tree = new SObjectTree();
+        tree.addObject(new Account()).addChild(new Contact()).addChild("Assets", new Asset());
+        tree.addObject(new Account());
+
+        final Class[] types = tree.objectTypes();
+        Arrays.sort(types, (final Class l, final Class r) -> l.getName().compareTo(r.getName()));
+
+        assertArrayEquals(new Class[] {Account.class, Asset.class, Contact.class}, types);
+    }
+
+    @Test
+    public void shouldSerializeToJson() throws JsonProcessingException {
+        final ObjectMapper mapper = new ObjectMapper();
+        mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+
+        final ObjectWriter writer = mapper.writerFor(SObjectTree.class);
+
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode account1 = new SObjectNode(tree, simpleAccount);
+        account1.addChild("Contacts", smith);
+        account1.addChild("Contacts", evans);
+        tree.addNode(account1);
+
+        final SObjectNode account2 = new SObjectNode(tree, simpleAccount2);
+        tree.addNode(account2);
+
+        final String json = writer.writeValueAsString(tree);
+
+        assertEquals("Should serialize to JSON as in Salesforce example",
+            "{\"records\":["//
+                + "{"//
+                + "\"attributes\":{\"referenceId\":\"ref1\",\"type\":\"Account\"},"//
+                + "\"Industry\":\"Banking\","//
+                + "\"Name\":\"SampleAccount\","//
+                + "\"NumberOfEmployees\":100,"//
+                + "\"Phone\":\"1234567890\","//
+                + "\"Website\":\"www.salesforce.com\","//
+                + "\"Contacts\":{"//
+                + "\"records\":["//
+                + "{"//
+                + "\"attributes\":{\"referenceId\":\"ref2\",\"type\":\"Contact\"},"//
+                + "\"Email\":\"sample@salesforce.com\","//
+                + "\"LastName\":\"Smith\","//
+                + "\"Title\":\"President\""//
+                + "},"//
+                + "{"//
+                + "\"attributes\":{\"referenceId\":\"ref3\",\"type\":\"Contact\"},"//
+                + "\"Email\":\"sample@salesforce.com\","//
+                + "\"LastName\":\"Evans\","//
+                + "\"Title\":\"Vice President\""//
+                + "}"//
+                + "]"//
+                + "}"//
+                + "},"//
+                + "{"//
+                + "\"attributes\":{\"referenceId\":\"ref4\",\"type\":\"Account\"},"//
+                + "\"Industry\":\"Banking\","//
+                + "\"Name\":\"SampleAccount2\","//
+                + "\"NumberOfEmployees\":100,"//
+                + "\"Phone\":\"1234567890\","//
+                + "\"Website\":\"www.salesforce2.com\""//
+                + "}"//
+                + "]"//
+                + "}",
+            json);
+    }
+
+    @Test
+    public void shouldSerializeToXml() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode account1 = new SObjectNode(tree, simpleAccount);
+        account1.addChild("Contacts", smith);
+        account1.addChild("Contacts", evans);
+        tree.addNode(account1);
+
+        final SObjectNode account2 = new SObjectNode(tree, simpleAccount2);
+        tree.addNode(account2);
+
+        final XStream xStream = new XStream();
+        xStream.processAnnotations(new Class[] {SObjectTree.class, Account.class, Contact.class, Asset.class});
+
+        final String xml = xStream.toXML(tree);
+
+        assertEquals("Should serialize to XML as in Salesforce example",
+            "<SObjectTreeRequest>\n"//
+                + "  <records type=\"Account\" referenceId=\"ref1\">\n"//
+                + "    <Name>SampleAccount</Name>\n"//
+                + "    <Phone>1234567890</Phone>\n"//
+                + "    <Website>www.salesforce.com</Website>\n"//
+                + "    <Industry>Banking</Industry>\n"//
+                + "    <NumberOfEmployees>100</NumberOfEmployees>\n"//
+                + "    <Contacts>\n"//
+                + "      <records type=\"Contact\" referenceId=\"ref2\">\n"//
+                + "        <Email>sample@salesforce.com</Email>\n"//
+                + "        <LastName>Smith</LastName>\n"//
+                + "        <Title>President</Title>\n"//
+                + "      </records>\n"//
+                + "      <records type=\"Contact\" referenceId=\"ref3\">\n"//
+                + "        <Email>sample@salesforce.com</Email>\n"//
+                + "        <LastName>Evans</LastName>\n"//
+                + "        <Title>Vice President</Title>\n"//
+                + "      </records>\n"//
+                + "    </Contacts>\n"//
+                + "  </records>\n"//
+                + "  <records type=\"Account\" referenceId=\"ref4\">\n"//
+                + "    <Name>SampleAccount2</Name>\n"//
+                + "    <Phone>1234567890</Phone>\n"//
+                + "    <Website>www.salesforce2.com</Website>\n"//
+                + "    <Industry>Banking</Industry>\n"//
+                + "    <NumberOfEmployees>100</NumberOfEmployees>\n"//
+                + "  </records>\n"//
+                + "</SObjectTreeRequest>",
+            xml);
+    }
+
+    @Test
+    public void shouldSetIdByReferences() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode account1 = new SObjectNode(tree, simpleAccount);
+        account1.addChild("Contacts", smith);
+        account1.addChild("Contacts", evans);
+        tree.addNode(account1);
+
+        final SObjectNode account2 = new SObjectNode(tree, simpleAccount2);
+        tree.addNode(account2);
+
+        tree.setIdFor("ref1", "id1");
+        tree.setIdFor("ref4", "id4");
+        tree.setIdFor("ref3", "id3");
+        tree.setIdFor("ref2", "id2");
+
+        assertEquals("id1", simpleAccount.getId());
+
+        assertEquals("id2", smith.getId());
+        assertEquals("id3", evans.getId());
+
+        assertEquals("id4", simpleAccount2.getId());
+    }
+
+    @Test
+    public void shouldSetIdByReferencesForNestedObjects() {
+        final SObjectTree tree = new SObjectTree();
+
+        final Account account = new Account();
+        final SObjectNode accountNode = new SObjectNode(tree, account);
+        tree.addNode(accountNode);
+
+        final Contact contact = new Contact();
+        final SObjectNode contactNode = new SObjectNode(tree, contact);
+        accountNode.addChild("Contacts", contactNode);
+
+        final Asset asset = new Asset();
+        final SObjectNode assetNode = new SObjectNode(tree, asset);
+        contactNode.addChild("Assets", assetNode);
+
+        assertEquals("ref1", accountNode.getAttributes().getReferenceId());
+        assertEquals("ref2", contactNode.getAttributes().getReferenceId());
+        assertEquals("ref3", assetNode.getAttributes().getReferenceId());
+
+        tree.setIdFor("ref1", "id1");
+        tree.setIdFor("ref3", "id3");
+        tree.setIdFor("ref2", "id2");
+
+        assertEquals("id1", account.getId());
+        assertEquals("id2", contact.getId());
+        assertEquals("id3", asset.getId());
+    }
+
+    @Test
+    public void shouldSetReferences() {
+        final SObjectTree tree = new SObjectTree();
+
+        final SObjectNode account1 = new SObjectNode(tree, simpleAccount);
+        account1.addChild("Contacts", smith);
+        account1.addChild("Contacts", evans);
+        tree.addNode(account1);
+
+        final SObjectNode account2 = new SObjectNode(tree, simpleAccount2);
+        tree.addNode(account2);
+
+        final SObjectNode simpleAccountFromTree = tree.records.get(0);
+        assertEquals("ref1", simpleAccountFromTree.getAttributes().getReferenceId());
+
+        final Iterator<SObjectNode> simpleAccountNodes = simpleAccountFromTree.getChildNodes().iterator();
+        assertEquals("ref2", simpleAccountNodes.next().getAttributes().getReferenceId());
+        assertEquals("ref3", simpleAccountNodes.next().getAttributes().getReferenceId());
+
+        assertEquals("ref4", account2.getAttributes().getReferenceId());
+    }
+
+    @Test
+    public void shouldSupportBuildingObjectTree() {
+        final SObjectTree tree = new SObjectTree();
+
+        tree.addObject(simpleAccount).addChildren("Contacts", smith, evans);
+
+        tree.addObject(simpleAccount2);
+
+        final SObjectNode firstAccountFromTree = tree.records.get(0);
+        assertSame(simpleAccount, firstAccountFromTree.getObject());
+        assertEquals("Account", firstAccountFromTree.getObjectType());
+
+        final Iterator<SObjectNode> simpleAccountNodes = firstAccountFromTree.getChildNodes().iterator();
+
+        final SObjectNode smithNode = simpleAccountNodes.next();
+        assertSame(smith, smithNode.getObject());
+        assertEquals("Contact", smithNode.getObjectType());
+
+        final SObjectNode evansNode = simpleAccountNodes.next();
+        assertSame(evans, evansNode.getObject());
+        assertEquals("Contact", evansNode.getObjectType());
+
+        final SObjectNode secondAccountFromTree = tree.records.get(1);
+        assertSame(simpleAccount2, secondAccountFromTree.getObject());
+        assertEquals("Account", secondAccountFromTree.getObjectType());
+    }
+
+    @Test
+    public void treeWithOneNodeShouldHaveSizeOfOne() {
+        final SObjectTree tree = new SObjectTree();
+        tree.addObject(new Account());
+
+        assertEquals(1, tree.size());
+    }
+
+    @Test
+    public void treeWithTwoNestedNodesShouldHaveSizeOfTwo() {
+        final SObjectTree tree = new SObjectTree();
+        final SObjectNode accountNode = tree.addObject(new Account());
+        accountNode.addChild("Contacts", new Contact());
+
+        assertEquals(2, tree.size());
+    }
+
+    @Test
+    public void treeWithTwoNodesShouldHaveSizeOfTwo() {
+        final SObjectTree tree = new SObjectTree();
+        tree.addObject(new Account());
+        tree.addObject(new Account());
+
+        assertEquals(2, tree.size());
+    }
+}