You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2013/12/20 13:54:44 UTC

git commit: [OLINGO-95] Introduce ExpandSelectTreeBuilder

Updated Branches:
  refs/heads/master 96e9d9e01 -> bb73dadd3


[OLINGO-95] Introduce ExpandSelectTreeBuilder

Tests for builder and serialization are done


Project: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/commit/bb73dadd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/tree/bb73dadd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/diff/bb73dadd

Branch: refs/heads/master
Commit: bb73dadd3f1e575cf8a781fb785fbacd8af134ca
Parents: 96e9d9e
Author: Christian Amend <ch...@apache.org>
Authored: Fri Dec 20 13:53:00 2013 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Fri Dec 20 13:53:00 2013 +0100

----------------------------------------------------------------------
 .../olingo/odata2/api/edm/EdmException.java     |   7 +
 .../olingo/odata2/api/rt/RuntimeDelegate.java   |   7 +
 .../odata2/api/uri/ExpandSelectTreeNode.java    |  91 +++++-
 .../odata2/core/rt/RuntimeDelegateImpl.java     |   8 +
 .../core/uri/ExpandSelectTreeNodeImpl.java      | 112 +++++++-
 .../src/main/resources/i18n.properties          |   4 +
 .../ExpandSelectProducerWithBuilderTest.java    | 204 ++++++++++++++
 .../core/ep/producer/XmlExpandProducerTest.java |  24 ++
 .../uri/ExpandSelectTreeNodeBuilderTest.java    | 282 +++++++++++++++++++
 9 files changed, 733 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmException.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmException.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmException.java
index 5c828cc..7e6f6fb 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmException.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmException.java
@@ -32,6 +32,13 @@ public class EdmException extends ODataMessageException {
 
   public static final MessageReference COMMON = createMessageReference(EdmException.class, "COMMON");
   public static final MessageReference PROVIDERPROBLEM = createMessageReference(EdmException.class, "PROVIDERPROBLEM");
+  public static final MessageReference PROPERTYNOTFOUND =
+      createMessageReference(EdmException.class, "PROPERTYNOTFOUND");
+  public static final MessageReference NAVIGATIONPROPERTYNOTFOUND =
+      createMessageReference(EdmException.class, "NAVIGATIONPROPERTYNOTFOUND");
+  public static final MessageReference MUSTBENAVIGATIONPROPERTY =
+      createMessageReference(EdmException.class, "MUSTBENAVIGATIONPROPERTY");
+  public static final MessageReference MUSTBEPROPERTY = createMessageReference(EdmException.class, "MUSTBEPROPERTY");
 
   public EdmException(final MessageReference messageReference) {
     super(messageReference);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
index e385c62..f0c1fc3 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
@@ -35,6 +35,7 @@ import org.apache.olingo.odata2.api.ep.EntityProviderException;
 import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
 import org.apache.olingo.odata2.api.processor.ODataResponse.ODataResponseBuilder;
 import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode.ExpandSelectTreeNodeBuilder;
 import org.apache.olingo.odata2.api.uri.UriParser;
 
 /**
@@ -107,6 +108,8 @@ public abstract class RuntimeDelegate {
 
     protected abstract BatchChangeSetPartBuilder createBatchChangeSetRequest();
 
+    public abstract ExpandSelectTreeNodeBuilder createExpandSelectTreeNodeBuilder();
+
   }
 
   /**
@@ -210,4 +213,8 @@ public abstract class RuntimeDelegate {
   public static BatchChangeSetPartBuilder createBatchChangeSetPartBuilder() {
     return RuntimeDelegate.getInstance().createBatchChangeSetRequest();
   }
+
+  public static ExpandSelectTreeNodeBuilder createExpandSelectTreeNodeBuilder() {
+    return RuntimeDelegate.getInstance().createExpandSelectTreeNodeBuilder();
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/uri/ExpandSelectTreeNode.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/uri/ExpandSelectTreeNode.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/uri/ExpandSelectTreeNode.java
index aa99a50..b12eb43 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/uri/ExpandSelectTreeNode.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/uri/ExpandSelectTreeNode.java
@@ -21,19 +21,22 @@ package org.apache.olingo.odata2.api.uri;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmException;
 import org.apache.olingo.odata2.api.edm.EdmProperty;
+import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
 
 /**
  * Expression tree node with information about selected properties and to be expanded links.
  * @org.apache.olingo.odata2.DoNotImplement
  * 
  */
-public interface ExpandSelectTreeNode {
+public abstract class ExpandSelectTreeNode {
 
   /**
    * Determines whether all properties (including navigation properties) have been selected.
    */
-  public boolean isAll();
+  public abstract boolean isAll();
 
   /**
    * <p>Gets the list of explicitly selected {@link EdmProperty properties}.</p>
@@ -41,7 +44,7 @@ public interface ExpandSelectTreeNode {
    * It is empty if {@link #isAll()} returns <code>true</code>.</p>
    * @return List of selected properties
    */
-  public List<EdmProperty> getProperties();
+  public abstract List<EdmProperty> getProperties();
 
   /**
    * Gets the links that have to be included or expanded.
@@ -49,5 +52,83 @@ public interface ExpandSelectTreeNode {
    * if that node is <code>null</code>, a deferred link has been requested,
    * otherwise the link must be expanded with information found in that node
    */
-  public Map<String, ExpandSelectTreeNode> getLinks();
-}
+  public abstract Map<String, ExpandSelectTreeNode> getLinks();
+
+  /**
+   * Creates a builder instance and sets the entitySet for this node.
+   * @param entitySet on which this node is based
+   * @return {@link ExpandSelectTreeNodeBuilder} to build the node
+   */
+  public static ExpandSelectTreeNodeBuilder entitySet(final EdmEntitySet entitySet) {
+    return ExpandSelectTreeNodeBuilder.newInstance().entitySet(entitySet);
+  }
+
+  /**
+   * Builder interface
+   */
+  public static abstract class ExpandSelectTreeNodeBuilder {
+
+    /**
+     * Uses the runtime delegate to create a new instance
+     * @return instance of {@link ExpandSelectTreeNodeBuilder}
+     */
+    private static ExpandSelectTreeNodeBuilder newInstance() {
+      return RuntimeDelegate.createExpandSelectTreeNodeBuilder();
+    }
+
+    /**
+     * Sets the entitySet for this node.
+     * @param entitySet must not be null
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     */
+    public abstract ExpandSelectTreeNodeBuilder entitySet(EdmEntitySet entitySet);
+
+    /**
+     * Will close this builder and return an {@link ExpandSelectTreeNode}. All properties and navigation properties will
+     * be validated if they exist for the entity set.
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     * @throws EdmException in case property or navigation property validation fails.
+     */
+    public abstract ExpandSelectTreeNode build() throws EdmException;
+
+    /**
+     * A list of properties which are selected. Selected means that they appear in the payload during serialization.
+     * MUST NOT CONTAIN navigation properties.
+     * @param selectedPropertyNames
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     */
+    public abstract ExpandSelectTreeNodeBuilder selectedProperties(List<String> selectedPropertyNames);
+
+    /**
+     * A list of selected links. Selected means they appear as links in the payload. If a link should be
+     * expanded they navigation property does not need to appear here but can. Expanded links will win over selected
+     * links.
+     * @param selectedNavigationPropertyNames
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     */
+    public abstract ExpandSelectTreeNodeBuilder selectedLinks(List<String> selectedNavigationPropertyNames);
+
+    /**
+     * Sets a link to be expanded with a custom node. With this the inline content can either also be expanded or
+     * selected. Custom nodes for a navigation properties will win over navigation properties which are also specified
+     * in the expanded links list. Example: if a link A is set with a custom node and A appears in the expanded link
+     * list it will be expanded with the custom node.
+     * @param navigationPropertyName
+     * @param expandNode must not be null
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     */
+    public abstract ExpandSelectTreeNodeBuilder customExpandedLink(String navigationPropertyName,
+        ExpandSelectTreeNode expandNode);
+
+    /**
+     * A list of expanded links. Expanded means their content will be shown as inline entry or feed in the payload but a
+     * callback MUST BE registered to get the content for the inline content. The inline content will appear with all
+     * properties and links. If this is not needed use the customExpandedLink method to set a custom node for this
+     * expanded link. Expanded links will win over selected links. If a custom node was set for a particular link it
+     * will win over a link that is specified in this list.
+     * @param navigationPropertyNames
+     * @return {@link ExpandSelectTreeNodeBuilder} for method chaining.
+     */
+    public abstract ExpandSelectTreeNodeBuilder expandedLinks(List<String> navigationPropertyNames);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
index cc1c6e7..f37b590 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
@@ -36,6 +36,7 @@ import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
 import org.apache.olingo.odata2.api.processor.ODataResponse.ODataResponseBuilder;
 import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
 import org.apache.olingo.odata2.api.rt.RuntimeDelegate.RuntimeDelegateInstance;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode.ExpandSelectTreeNodeBuilder;
 import org.apache.olingo.odata2.api.uri.UriParser;
 import org.apache.olingo.odata2.core.ODataRequestImpl;
 import org.apache.olingo.odata2.core.ODataResponseImpl;
@@ -48,6 +49,7 @@ import org.apache.olingo.odata2.core.edm.provider.EdmImplProv;
 import org.apache.olingo.odata2.core.edm.provider.EdmxProvider;
 import org.apache.olingo.odata2.core.ep.ProviderFacadeImpl;
 import org.apache.olingo.odata2.core.processor.ODataSingleProcessorService;
+import org.apache.olingo.odata2.core.uri.ExpandSelectTreeNodeImpl;
 import org.apache.olingo.odata2.core.uri.UriParserImpl;
 
 /**
@@ -129,4 +131,10 @@ public class RuntimeDelegateImpl extends RuntimeDelegateInstance {
     return batchChangeSetRequest.new BatchChangeSetRequestBuilderImpl();
   }
 
+  @Override
+  public ExpandSelectTreeNodeBuilder createExpandSelectTreeNodeBuilder() {
+    ExpandSelectTreeNodeImpl expandSelectTreeNode = new ExpandSelectTreeNodeImpl();
+    return expandSelectTreeNode.new ExpandSelectTreeNodeBuilderImpl();
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeImpl.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeImpl.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeImpl.java
index d14e1bb..ba3f624 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeImpl.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeImpl.java
@@ -26,8 +26,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmEntityType;
 import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
 import org.apache.olingo.odata2.api.edm.EdmProperty;
+import org.apache.olingo.odata2.api.edm.EdmTyped;
 import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
@@ -35,7 +39,7 @@ import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
 /**
  *  
  */
-public class ExpandSelectTreeNodeImpl implements ExpandSelectTreeNode {
+public class ExpandSelectTreeNodeImpl extends ExpandSelectTreeNode {
 
   public enum AllKinds {
     IMPLICITLYTRUE(true), EXPLICITLYTRUE(true), FALSE(false);
@@ -161,4 +165,110 @@ public class ExpandSelectTreeNodeImpl implements ExpandSelectTreeNode {
       throw new ODataRuntimeException("EdmException: ", e);
     }
   }
+
+  public class ExpandSelectTreeNodeBuilderImpl extends ExpandSelectTreeNodeBuilder {
+
+    EdmEntitySet entitySet;
+    private List<String> selectedPropertyNames;
+    private List<String> selectedNavigationPropertyNames;
+    private Map<String, ExpandSelectTreeNode> customExpandedNavigationProperties;
+    private List<String> expandedNavigationPropertyNames;
+
+    @Override
+    public ExpandSelectTreeNodeBuilder entitySet(final EdmEntitySet entitySet) {
+      this.entitySet = entitySet;
+      return this;
+    }
+
+    @Override
+    public ExpandSelectTreeNode build() throws EdmException {
+      EdmEntityType entityType = entitySet.getEntityType();
+      if (selectedPropertyNames != null) {
+        for (String propertyName : selectedPropertyNames) {
+          EdmTyped property = entityType.getProperty(propertyName);
+          if (property == null) {
+            throw new EdmException(EdmException.PROPERTYNOTFOUND.addContent(propertyName));
+          } else if (!(property instanceof EdmProperty)) {
+            throw new EdmException(EdmException.MUSTBEPROPERTY.addContent(propertyName));
+          }
+          addProperty((EdmProperty) property);
+        }
+      }
+
+      if (selectedNavigationPropertyNames != null) {
+        setAllKindFalse();
+        for (String navigationPropertyName : selectedNavigationPropertyNames) {
+          EdmTyped navigationProperty = entityType.getProperty(navigationPropertyName);
+          if (navigationProperty == null) {
+            throw new EdmException(EdmException.NAVIGATIONPROPERTYNOTFOUND.addContent(navigationPropertyName));
+          } else if (!(navigationProperty instanceof EdmNavigationProperty)) {
+            throw new EdmException(EdmException.MUSTBENAVIGATIONPROPERTY.addContent(navigationPropertyName));
+          }
+          putLink(navigationPropertyName, null);
+        }
+      }
+
+      if (expandedNavigationPropertyNames != null) {
+        ExpandSelectTreeNodeImpl subNode = new ExpandSelectTreeNodeImpl();
+        subNode.setExplicitlySelected();
+        for (String navigationPropertyName : expandedNavigationPropertyNames) {
+          EdmTyped navigationProperty = entityType.getProperty(navigationPropertyName);
+          if (navigationProperty == null) {
+            throw new EdmException(EdmException.NAVIGATIONPROPERTYNOTFOUND.addContent(navigationPropertyName));
+          } else if (!(navigationProperty instanceof EdmNavigationProperty)) {
+            throw new EdmException(EdmException.MUSTBENAVIGATIONPROPERTY.addContent(navigationPropertyName));
+          }
+          putLink(navigationPropertyName, subNode);
+        }
+      }
+
+      if (customExpandedNavigationProperties != null) {
+        for (Map.Entry<String, ExpandSelectTreeNode> entry : customExpandedNavigationProperties.entrySet()) {
+          EdmTyped navigationProperty = entityType.getProperty(entry.getKey());
+          if (navigationProperty == null) {
+            throw new EdmException(EdmException.NAVIGATIONPROPERTYNOTFOUND.addContent(entry.getKey()));
+          }
+          if (!(navigationProperty instanceof EdmNavigationProperty)) {
+            throw new EdmException(EdmException.MUSTBENAVIGATIONPROPERTY.addContent(entry.getKey()));
+          }
+          putLink(entry.getKey(), (ExpandSelectTreeNodeImpl) entry.getValue());
+        }
+      }
+
+      return ExpandSelectTreeNodeImpl.this;
+    }
+
+    @Override
+    public ExpandSelectTreeNodeBuilder selectedProperties(final List<String> selectedPropertyNames) {
+      this.selectedPropertyNames = selectedPropertyNames;
+      return this;
+    }
+
+    @Override
+    public ExpandSelectTreeNodeBuilder selectedLinks(final List<String> selectedNavigationPropertyNames) {
+      this.selectedNavigationPropertyNames = selectedNavigationPropertyNames;
+      return this;
+    }
+
+    @Override
+    public ExpandSelectTreeNodeBuilder
+        customExpandedLink(final String navigationPropertyName, final ExpandSelectTreeNode expandNode) {
+      if (expandNode == null) {
+        throw new ODataRuntimeException("ExpandNode must not be null");
+      }
+      if (customExpandedNavigationProperties == null) {
+        customExpandedNavigationProperties = new HashMap<String, ExpandSelectTreeNode>();
+      }
+      customExpandedNavigationProperties.put(navigationPropertyName, expandNode);
+      return this;
+    }
+
+    @Override
+    public ExpandSelectTreeNodeBuilder expandedLinks(final List<String> navigationPropertyNames) {
+      expandedNavigationPropertyNames = navigationPropertyNames;
+      return this;
+    }
+
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/main/resources/i18n.properties
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/resources/i18n.properties b/odata2-lib/odata-core/src/main/resources/i18n.properties
index b28bf50..08c08a1 100644
--- a/odata2-lib/odata-core/src/main/resources/i18n.properties
+++ b/odata2-lib/odata-core/src/main/resources/i18n.properties
@@ -67,6 +67,10 @@ org.apache.olingo.odata2.api.edm.EdmLiteralException.UNKNOWNLITERAL=Unknown lite
 
 org.apache.olingo.odata2.api.edm.EdmException.COMMON=An exception occurred.
 org.apache.olingo.odata2.api.edm.EdmException.PROVIDERPROBLEM=A problem has been detected in the metadata provided by the EDM provider.
+org.apache.olingo.odata2.api.edm.EdmException.PROPERTYNOTFOUND=Invalid property with name '%1$s'
+org.apache.olingo.odata2.api.edm.EdmException.NAVIGATIONPROPERTYNOTFOUND=Invalid navigation property with name '%1$s'
+org.apache.olingo.odata2.api.edm.EdmException.MUSTBENAVIGATIONPROPERTY=The found typed value for '%1$s' must be a navigation property but is not.
+org.apache.olingo.odata2.api.edm.EdmException.MUSTBEPROPERTY=The found typed value for '%1$s' must be a property but is not.
 
 org.apache.olingo.odata2.api.edm.EdmSimpleTypeException.COMMON=An exception occurred.
 org.apache.olingo.odata2.api.edm.EdmSimpleTypeException.LITERAL_KIND_MISSING=The literal kind is missing.

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/ExpandSelectProducerWithBuilderTest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/ExpandSelectProducerWithBuilderTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/ExpandSelectProducerWithBuilderTest.java
new file mode 100644
index 0000000..6eabdfc
--- /dev/null
+++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/ExpandSelectProducerWithBuilderTest.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * 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.olingo.odata2.core.ep.producer;
+
+import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists;
+import static org.custommonkey.xmlunit.XMLAssert.assertXpathNotExists;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.ODataCallback;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
+import org.apache.olingo.odata2.api.ep.EntityProvider;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.processor.ODataResponse;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
+import org.apache.olingo.odata2.core.ep.AbstractProviderTest;
+import org.apache.olingo.odata2.testutil.helper.StringHelper;
+import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Test;
+
+public class ExpandSelectProducerWithBuilderTest extends AbstractProviderTest {
+
+  public class LocalCallback implements OnWriteEntryContent {
+
+    @Override
+    public WriteEntryCallbackResult retrieveEntryResult(final WriteEntryCallbackContext context)
+        throws ODataApplicationException {
+      WriteEntryCallbackResult writeEntryCallbackResult = new WriteEntryCallbackResult();
+      EntityProviderWriteProperties inlineProperties =
+          EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).expandSelectTree(
+              context.getCurrentExpandSelectTreeNode()).build();
+      writeEntryCallbackResult.setInlineProperties(inlineProperties);
+      Map<String, Object> buildingData = new HashMap<String, Object>();
+      buildingData.put("Id", "1");
+      buildingData.put("Name", "BuildingName");
+      writeEntryCallbackResult.setEntryData(buildingData);
+      return writeEntryCallbackResult;
+    }
+
+  }
+
+  public ExpandSelectProducerWithBuilderTest(final StreamWriterImplType type) {
+    super(type);
+  }
+
+  @Test
+  public void selectOnlyProperties() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedProperties = new ArrayList<String>(roomData.keySet());
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedProperties).build();
+
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).expandSelectTree(expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathExists("/a:entry/a:content/m:properties", xml);
+    assertXpathNotExists("/a:entry/a:link[@type]", xml);
+  }
+
+  @Test
+  public void selectOnlyLinks() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedNavigationProperties = new ArrayList<String>();
+    selectedNavigationProperties.add("nr_Building");
+    selectedNavigationProperties.add("nr_Employees");
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedLinks(selectedNavigationProperties).build();
+
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).expandSelectTree(expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathNotExists("/a:entry/a:content/m:properties", xml);
+    assertXpathExists("/a:entry/a:link[@type]", xml);
+  }
+
+  @Test
+  public void selectIdAndBuildingLink() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedNavigationProperties = new ArrayList<String>();
+    selectedNavigationProperties.add("nr_Building");
+
+    List<String> selectedProperties = new ArrayList<String>();
+    selectedProperties.add("Id");
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedProperties).selectedLinks(
+            selectedNavigationProperties).build();
+
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).expandSelectTree(expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathExists("/a:entry/a:content/m:properties", xml);
+    assertXpathExists("/a:entry/a:link[@type]", xml);
+  }
+
+  @Test
+  public void expandBuilding() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> expandedNavigationProperties = new ArrayList<String>();
+    expandedNavigationProperties.add("nr_Building");
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).expandedLinks(expandedNavigationProperties).build();
+
+    Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
+    callbacks.put("nr_Building", new LocalCallback());
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).callbacks(callbacks).expandSelectTree(
+            expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathExists("/a:entry/a:content/m:properties", xml);
+    assertXpathExists("/a:entry/a:link[@type]/m:inline", xml);
+  }
+
+  @Test
+  public void expandBuildingAndSelectIdFromRoom() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> expandedNavigationProperties = new ArrayList<String>();
+    expandedNavigationProperties.add("nr_Building");
+
+    List<String> selectedProperties = new ArrayList<String>();
+    selectedProperties.add("Id");
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedProperties).expandedLinks(
+            expandedNavigationProperties).build();
+
+    Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
+    callbacks.put("nr_Building", new LocalCallback());
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).callbacks(callbacks).expandSelectTree(
+            expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathExists("/a:entry/a:content/m:properties/d:Id", xml);
+    assertXpathNotExists("/a:entry/a:content/m:properties/d:Name", xml);
+    assertXpathExists("/a:entry/a:link[@type]/m:inline", xml);
+  }
+
+  @Test
+  public void customExpandBuildingAndSelectIdFromCustomNode() throws Exception {
+    EdmEntitySet roomsSet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+
+    List<String> selectedPropertiesAtCustomProperties = new ArrayList<String>();
+    selectedPropertiesAtCustomProperties.add("Id");
+    EdmEntitySet buildingsSet =
+        roomsSet.getRelatedEntitySet((EdmNavigationProperty) roomsSet.getEntityType().getProperty("nr_Building"));
+    ExpandSelectTreeNode customNode =
+        ExpandSelectTreeNode.entitySet(buildingsSet).selectedProperties(selectedPropertiesAtCustomProperties).build();
+
+    ExpandSelectTreeNode expandSelectTree =
+        ExpandSelectTreeNode.entitySet(roomsSet).customExpandedLink("nr_Building", customNode).build();
+
+    Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
+    callbacks.put("nr_Building", new LocalCallback());
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.fromProperties(DEFAULT_PROPERTIES).callbacks(callbacks).expandSelectTree(
+            expandSelectTree).build();
+    ODataResponse entry = EntityProvider.writeEntry("application/xml", roomsSet, roomData, properties);
+
+    String xml = StringHelper.inputStreamToString((InputStream) entry.getEntity());
+    assertXpathExists("/a:entry/a:content/m:properties", xml);
+    assertXpathExists("/a:entry/a:link[@type]/m:inline", xml);
+    assertXpathExists("/a:entry/a:link[@type]/m:inline/a:entry/a:content/m:properties/d:Id", xml);
+    assertXpathNotExists("/a:entry/a:link[@type]/m:inline/a:entry/a:content/m:properties/d:Name", xml);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/XmlExpandProducerTest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/XmlExpandProducerTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/XmlExpandProducerTest.java
index c9ff16a..c4ba63c 100644
--- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/XmlExpandProducerTest.java
+++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/XmlExpandProducerTest.java
@@ -34,6 +34,7 @@ import java.util.Map;
 
 import org.apache.olingo.odata2.api.ODataCallback;
 import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
 import org.apache.olingo.odata2.api.edm.EdmException;
 import org.apache.olingo.odata2.api.ep.EntityProviderException;
 import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
@@ -104,6 +105,29 @@ public class XmlExpandProducerTest extends AbstractProviderTest {
   }
 
   @Test
+  public void expandSelectedEmployeesWithBuilder() throws Exception {
+    EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add("nr_Employees");
+    ExpandSelectTreeNode selectTree =
+        ExpandSelectTreeNode.entitySet(entitySet).expandedLinks(navigationPropertyNames).build();
+
+    HashMap<String, ODataCallback> callbacksRoom = createCallbacks("Rooms");
+    EntityProviderWriteProperties properties =
+        EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree(selectTree).callbacks(callbacksRoom)
+            .build();
+    AtomEntityProvider provider = createAtomEntityProvider();
+    ODataResponse response =
+        provider.writeEntry(entitySet, roomData,
+            properties);
+
+    String xmlString = verifyResponse(response);
+    assertXpathNotExists("/a:entry/m:properties", xmlString);
+    assertXpathExists("/a:entry/a:link", xmlString);
+    verifyEmployees(employeeXPathString, xmlString);
+  }
+
+  @Test
   public void expandSelectedEmployeesNull() throws Exception {
     ExpandSelectTreeNode selectTree = getSelectExpandTree("Rooms('1')", "nr_Employees", "nr_Employees");
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/bb73dadd/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeBuilderTest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeBuilderTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeBuilderTest.java
new file mode 100644
index 0000000..ec9b3fd
--- /dev/null
+++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/ExpandSelectTreeNodeBuilderTest.java
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * 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.olingo.odata2.core.uri;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+import org.apache.olingo.odata2.testutil.fit.BaseTest;
+import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ExpandSelectTreeNodeBuilderTest extends BaseTest {
+
+  Edm edm;
+
+  @Before
+  public void setupEdm() throws Exception {
+    edm = MockFacade.getMockEdm();
+  }
+
+  @Test
+  public void initialBuildWithOnlyEntitySet() throws Exception {
+    ExpandSelectTreeNode node = ExpandSelectTreeNode.entitySet(mock(EdmEntitySet.class)).build();
+    assertNotNull(node);
+    assertTrue(node.isAll());
+    assertTrue(node.getProperties().isEmpty());
+    assertTrue(node.getLinks().isEmpty());
+  }
+
+  @Test
+  public void buildWithRightSelectedPropertiesOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedPropertyNames = new ArrayList<String>();
+    selectedPropertyNames.add("Id");
+    ExpandSelectTreeNode node =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedPropertyNames).build();
+    assertNotNull(node);
+    assertFalse(node.isAll());
+    assertFalse(node.getProperties().isEmpty());
+    assertTrue(node.getLinks().isEmpty());
+
+    assertEquals(1, node.getProperties().size());
+    assertEquals("Id", node.getProperties().get(0).getName());
+  }
+
+  @Test
+  public void buildWithRightSelectedNavigationPropertiesOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedNavigationPropertyNames = new ArrayList<String>();
+    selectedNavigationPropertyNames.add("nr_Employees");
+    ExpandSelectTreeNode node =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedLinks(selectedNavigationPropertyNames).build();
+    assertNotNull(node);
+    assertFalse(node.isAll());
+    assertTrue(node.getProperties().isEmpty());
+    assertFalse(node.getLinks().isEmpty());
+
+    assertEquals(1, node.getLinks().size());
+    assertTrue(node.getLinks().containsKey("nr_Employees"));
+    assertNull(node.getLinks().get("nr_Employees"));
+  }
+
+  @Test
+  public void buildWithRightExpandedNavigationPropertiesOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    String navigationPropertyName = "nr_Employees";
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add(navigationPropertyName);
+
+    ExpandSelectTreeNode node =
+        ExpandSelectTreeNode.entitySet(roomsSet).expandedLinks(navigationPropertyNames).build();
+    assertExpandedNode(node);
+
+    assertLinksWithOneNavigationProperty(navigationPropertyName, node);
+  }
+
+  @Test
+  public void buildWithRightCustomExpandedNavigationPropertyOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    ExpandSelectTreeNode expandNode = ExpandSelectTreeNode.entitySet(mock(EdmEntitySet.class)).build();
+    String navigationPropertyName = "nr_Employees";
+
+    ExpandSelectTreeNode node =
+        ExpandSelectTreeNode.entitySet(roomsSet).customExpandedLink(navigationPropertyName, expandNode).build();
+    assertExpandedNode(node);
+
+    assertLinksWithOneNavigationProperty(navigationPropertyName, node);
+  }
+
+  @Test
+  public void expandedAndCustomExpandedNavPropCustomMustWin() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    ExpandSelectTreeNode expandNode = ExpandSelectTreeNode.entitySet(mock(EdmEntitySet.class)).build();
+    String navigationPropertyName = "nr_Employees";
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add(navigationPropertyName);
+
+    ExpandSelectTreeNode node = ExpandSelectTreeNode.entitySet(roomsSet)
+        .customExpandedLink(navigationPropertyName, expandNode).expandedLinks(navigationPropertyNames).build();
+    assertExpandedNode(node);
+
+    assertLinksWithOneNavigationProperty(navigationPropertyName, node);
+    assertEquals(expandNode, node.getLinks().get(navigationPropertyName));
+  }
+
+  @Test
+  public void selectedPropertiesAndExpandedNavigationProperties() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedPropertyNames = new ArrayList<String>();
+    selectedPropertyNames.add("Id");
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add("nr_Employees");
+    ExpandSelectTreeNode node = ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedPropertyNames)
+        .expandedLinks(navigationPropertyNames).build();
+    assertNotNull(node);
+    assertFalse(node.isAll());
+    assertFalse(node.getProperties().isEmpty());
+    assertFalse(node.getLinks().isEmpty());
+
+    assertEquals(1, node.getProperties().size());
+    assertEquals("Id", node.getProperties().get(0).getName());
+
+    assertLinksWithOneNavigationProperty("nr_Employees", node);
+  }
+
+  @Test
+  public void selectedNavPropsAndExpandedNavPropsExpandedMustNotBeNull() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    String navigationPropertyName = "nr_Employees";
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add(navigationPropertyName);
+
+    ExpandSelectTreeNode node = ExpandSelectTreeNode.entitySet(roomsSet).selectedLinks(navigationPropertyNames)
+        .expandedLinks(navigationPropertyNames).build();
+    assertNotNull(node);
+    assertFalse(node.isAll());
+    assertTrue(node.getProperties().isEmpty());
+    assertFalse(node.getLinks().isEmpty());
+
+    assertLinksWithOneNavigationProperty(navigationPropertyName, node);
+  }
+
+  @Test
+  public void selectedExpandedAndCustomExpanded() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedPropertyNames = new ArrayList<String>();
+    selectedPropertyNames.add("Id");
+    List<String> navigationPropertyNames = new ArrayList<String>();
+    navigationPropertyNames.add("nr_Employees");
+    ExpandSelectTreeNode expandNode = ExpandSelectTreeNode.entitySet(mock(EdmEntitySet.class)).build();
+
+    ExpandSelectTreeNode node =
+        ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedPropertyNames).expandedLinks(
+            navigationPropertyNames).customExpandedLink("nr_Building", expandNode).build();
+
+    assertNotNull(node);
+    assertFalse(node.isAll());
+    assertFalse(node.getProperties().isEmpty());
+    assertFalse(node.getLinks().isEmpty());
+
+    assertEquals(1, node.getProperties().size());
+    assertEquals("Id", node.getProperties().get(0).getName());
+
+    assertEquals(2, node.getLinks().size());
+    assertTrue(node.getLinks().containsKey("nr_Employees"));
+    assertNotNull(node.getLinks().get("nr_Employees"));
+
+    assertTrue(node.getLinks().containsKey("nr_Building"));
+    assertNotNull(node.getLinks().get("nr_Building"));
+    assertEquals(expandNode, node.getLinks().get("nr_Building"));
+  }
+
+  @Test(expected = EdmException.class)
+  public void buildWithWrongSelectedPropertiesOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedPropertyNames = new ArrayList<String>();
+    selectedPropertyNames.add("WrongProperty");
+    ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedPropertyNames).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void buildWithWrongSelectedNavigationPropertiesOnly() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedNavigationPropertyNames = new ArrayList<String>();
+    selectedNavigationPropertyNames.add("WrongProperty");
+    ExpandSelectTreeNode.entitySet(roomsSet).selectedLinks(selectedNavigationPropertyNames).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void propertyInNavigationPropertiesList() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedNavigationPropertyNames = new ArrayList<String>();
+    selectedNavigationPropertyNames.add("Id");
+    ExpandSelectTreeNode.entitySet(roomsSet).selectedLinks(selectedNavigationPropertyNames).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void navigationPropertyInPropertiesList() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> selectedPropertyNames = new ArrayList<String>();
+    selectedPropertyNames.add("nr_Building");
+    ExpandSelectTreeNode.entitySet(roomsSet).selectedProperties(selectedPropertyNames).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void propertyInExpandedNavigationPropertyList() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> expandedNavigationPropertyNames = new ArrayList<String>();
+    expandedNavigationPropertyNames.add("Id");
+    ExpandSelectTreeNode.entitySet(roomsSet).expandedLinks(expandedNavigationPropertyNames).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void propertyInCustomExpandedNavigationPropertyList() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    ExpandSelectTreeNode.entitySet(roomsSet).customExpandedLink("Id", mock(ExpandSelectTreeNode.class)).build();
+  }
+
+  @Test(expected = ODataRuntimeException.class)
+  public void nullNodeInCustomExpandedProperty() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    ExpandSelectTreeNode.entitySet(roomsSet).customExpandedLink("nr_Building", null).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void wrongNavigationPropertyInCustomExpand() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    ExpandSelectTreeNode.entitySet(roomsSet).customExpandedLink("Wrong", mock(ExpandSelectTreeNode.class)).build();
+  }
+
+  @Test(expected = EdmException.class)
+  public void wrongNavigationPropertyInExpandedList() throws Exception {
+    EdmEntitySet roomsSet = edm.getDefaultEntityContainer().getEntitySet("Rooms");
+    List<String> expandedNavigationPropertyNames = new ArrayList<String>();
+    expandedNavigationPropertyNames.add("Wrong");
+    ExpandSelectTreeNode.entitySet(roomsSet).expandedLinks(expandedNavigationPropertyNames).build();
+  }
+
+  private void
+      assertLinksWithOneNavigationProperty(final String navigationPropertyName, final ExpandSelectTreeNode node) {
+    assertEquals(1, node.getLinks().size());
+    assertTrue(node.getLinks().containsKey(navigationPropertyName));
+    assertNotNull(node.getLinks().get(navigationPropertyName));
+  }
+
+  private void assertExpandedNode(final ExpandSelectTreeNode node) {
+    assertNotNull(node);
+    assertTrue(node.isAll());
+    assertTrue(node.getProperties().isEmpty());
+    assertFalse(node.getLinks().isEmpty());
+  }
+
+}