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 2016/04/18 15:07:49 UTC

[1/3] olingo-odata4 git commit: [OLINGO-935]URI-parser support of the data aggregation extension

Repository: olingo-odata4
Updated Branches:
  refs/heads/master 824c174d7 -> 6553e9508


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java
new file mode 100644
index 0000000..87a4281
--- /dev/null
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ApplyParserTest.java
@@ -0,0 +1,676 @@
+/*
+ * 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.server.core.uri.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edmx.EdmxReference;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriInfoKind;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceKind;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop.Method;
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
+import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+import org.apache.olingo.server.api.uri.queryoption.apply.Identity;
+import org.apache.olingo.server.api.uri.queryoption.apply.Search;
+import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
+import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
+import org.apache.olingo.server.core.uri.UriInfoImpl;
+import org.apache.olingo.server.core.uri.testutil.ExpandValidator;
+import org.apache.olingo.server.core.uri.testutil.FilterValidator;
+import org.apache.olingo.server.core.uri.testutil.ResourceValidator;
+import org.apache.olingo.server.core.uri.testutil.TestUriValidator;
+import org.apache.olingo.server.core.uri.testutil.TestValidator;
+import org.apache.olingo.server.core.uri.validator.UriValidationException;
+import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider;
+import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
+import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider;
+import org.apache.olingo.server.tecsvc.provider.FunctionProvider;
+import org.apache.olingo.server.tecsvc.provider.PropertyProvider;
+import org.junit.Test;
+
+/** Tests of the $apply parser inspired by the ABNF test cases. */
+public class ApplyParserTest {
+
+  private static final OData odata = OData.newInstance();
+  private static final Edm edm = odata.createServiceMetadata(
+      new EdmTechProvider(), Collections.<EdmxReference> emptyList()).getEdm();
+
+  @Test
+  public void aggregate() throws Exception {
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with sum as s)")
+        .is(Aggregate.class)
+        .goAggregate(0).isStandardMethod(StandardMethod.SUM).isAlias("s")
+        .goExpression().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with min as m)")
+        .goAggregate(0).isStandardMethod(StandardMethod.MIN).isAlias("m");
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with max as m)")
+        .goAggregate(0).isStandardMethod(StandardMethod.MAX).isAlias("m");
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with average as a)")
+        .goAggregate(0).isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with countdistinct as c)")
+        .goAggregate(0).isStandardMethod(StandardMethod.COUNT_DISTINCT).isAlias("c");
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with custom.aggregate as c)")
+        .is(Aggregate.class)
+        .goAggregate(0).isCustomMethod(new FullQualifiedName("custom", "aggregate")).isAlias("c");
+
+    parseEx("ESTwoKeyNav", "aggregate()")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with sum)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 as s)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with SUM as s)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyString with countdistinct as PropertyInt16)")
+        .isExSemantic(UriParserSemanticException.MessageKeys.IS_PROPERTY);
+  }
+
+  @Test
+  public void aggregateExpression() throws Exception {
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 mul PropertyComp/PropertyInt16 with sum as s)")
+        .is(Aggregate.class)
+        .goAggregate(0).isStandardMethod(StandardMethod.SUM)
+        .goExpression().isBinary(BinaryOperatorKind.MUL)
+        .left().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false)
+        .goUpFilterValidator().root()
+        .right().goPath().first().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTPrimComp, false)
+        .n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+
+    parse("ESTwoKeyNav",
+        "aggregate(NavPropertyETKeyNavMany(PropertyInt16 mul NavPropertyETTwoKeyNavOne/PropertyInt16 with sum as s))")
+        .goAggregate(0)
+        .goInlineAggregateExpression().isStandardMethod(StandardMethod.SUM)
+        .goUpAggregate()
+        .goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
+
+    parseEx("ESTwoKeyNav", "aggregate((PropertyInt16 mul 2 with sum as s))")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+  }
+
+  @Test
+  public void aggregateCount() throws Exception {
+    parse("ESTwoKeyNav", "aggregate($count as count)")
+        .is(Aggregate.class)
+        .goAggregate(0).goPath().first().isCount();
+
+    parseEx("ESTwoKeyNav", "aggregate($count)").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate($count with sum as count)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+  }
+
+  @Test
+  public void aggregateFrom() throws Exception {
+    parse("ESTwoKeyNav", "aggregate(PropertyInt16 with sum as s from CollPropertyComp with average)")
+        .goAggregate(0).isStandardMethod(StandardMethod.SUM)
+        .goFrom(0).isStandardMethod(StandardMethod.AVERAGE)
+        .goExpression().goPath().first()
+        .isComplexProperty("CollPropertyComp", ComplexTypeProvider.nameCTPrimComp, true);
+    parse("ESTwoKeyNav",
+        "aggregate(PropertyInt16 with sum as s from CollPropertyComp with average from CollPropertyString with max)")
+        .goAggregate(0).isStandardMethod(StandardMethod.SUM)
+        .goFrom(0).isStandardMethod(StandardMethod.AVERAGE)
+        .goUpAggregate().goFrom(1).isStandardMethod(StandardMethod.MAX);
+    parse("ESTwoKeyNav", "aggregate(customAggregate as a from CollPropertyComp with average)")
+        .goAggregate(0).goFrom(0).isStandardMethod(StandardMethod.AVERAGE);
+
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 as a from CollPropertyComp with average)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with sum from CollPropertyComp with average)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+  }
+
+  @Test
+  public void identity() throws Exception {
+    parse("ESTwoKeyNav", "identity").is(Identity.class);
+  }
+
+  @Test
+  public void compute() throws Exception {
+    parse("ESTwoKeyNav", "compute(PropertyInt16 mul NavPropertyETKeyNavOne/PropertyInt16 as p)")
+        .is(Compute.class)
+        .goCompute(0).isAlias("p").goExpression().isBinary(BinaryOperatorKind.MUL)
+        .left().isMember().goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav", "compute(PropertyInt16 mul 2 as p,day(now()) as d)")
+        .goCompute(0).isAlias("p")
+        .goUp().goCompute(1).isAlias("d")
+        .goExpression().isMethod(MethodKind.DAY, 1).goParameter(0).isMethod(MethodKind.NOW, 0);
+
+    parseEx("ESTwoKeyNav", "compute(PropertyInt16)")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESTwoKeyNav", "compute(PropertyComp as c)")
+        .isExSemantic(UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES);
+  }
+
+  @Test
+  public void concat() throws Exception {
+    parse("ESTwoKeyNav", "concat(topcount(2,PropertyInt16),bottomcount(2,PropertyInt16))")
+        .is(Concat.class)
+        .goConcat(0).goBottomTop().isMethod(Method.TOP_COUNT)
+        .goUp().goUp()
+        .goConcat(1).goBottomTop().isMethod(Method.BOTTOM_COUNT).goNumber().isLiteral("2");
+  }
+
+  @Test
+  public void expand() throws Exception {
+    parse("ESTwoKeyNav", "expand(NavPropertyETKeyNavMany,filter(PropertyInt16 gt 2))")
+        .is(Expand.class).goExpand()
+        .goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true)
+        .goUpExpandValidator().isFilterSerialized("<<PropertyInt16> gt <2>>");
+    parse("ESTwoKeyNav",
+        "expand(NavPropertyETKeyNavMany,expand(NavPropertyETTwoKeyNavMany,filter(PropertyInt16 gt 2)))")
+        .is(Expand.class).goExpand().goExpand().isFilterSerialized("<<PropertyInt16> gt <2>>");
+    parse("ESTwoKeyNav",
+        "expand(NavPropertyETKeyNavMany,expand(NavPropertyETTwoKeyNavMany,filter(PropertyInt16 gt 2)),"
+        + "expand(NavPropertyETTwoKeyNavOne,expand(NavPropertyETKeyNavMany)))")
+        .is(Expand.class).goExpand().goExpand().next().goExpand()
+        .goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
+  }
+
+  @Test
+  public void search() throws Exception {
+    parse("ESTwoKeyNav", "search(String)").isSearch("'String'");
+  }
+
+  @Test
+  public void filter() throws Exception {
+    parse("ESTwoKeyNav", "filter(PropertyInt16 gt 3)")
+        .is(Filter.class)
+        .goFilter().isBinary(BinaryOperatorKind.GT)
+        .left().isMember().goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+  }
+
+  @Test
+  public void bottomTop() throws Exception {
+    parse("ESTwoKeyNav", "topcount(2,PropertyInt16)")
+        .goBottomTop().isMethod(Method.TOP_COUNT)
+        .goNumber().isLiteralType(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte))
+        .isLiteral("2");
+    parse("ESTwoKeyNav", "topsum(2,PropertyInt16)")
+        .goBottomTop().isMethod(Method.TOP_SUM)
+        .goValue().isMember().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav", "toppercent(2,PropertyInt16)").goBottomTop().isMethod(Method.TOP_PERCENT);
+
+    parse("ESTwoKeyNav", "bottomcount(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_COUNT);
+    parse("ESTwoKeyNav", "bottomsum(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_SUM);
+    parse("ESTwoKeyNav", "bottompercent(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_PERCENT);
+
+    parseEx("ESTwoKeyNav", "bottompercent(1.2,PropertyInt16)")
+        .isExSemantic(UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE);
+    parseEx("ESTwoKeyNav", "bottompercent(2,PropertyString)")
+        .isExSemantic(UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE);
+  }
+
+  @Test
+  public void customFunction() throws Exception {
+    parse("ESBaseTwoKeyNav", "Namespace1_Alias.BFCESBaseTwoKeyNavRTESBaseTwoKey()")
+        .isCustomFunction(FunctionProvider.nameBFCESBaseTwoKeyNavRTESBaseTwoKey);
+    parse("ESKeyNav(1)/CollPropertyComp", "Namespace1_Alias.BFCCollCTPrimCompRTESAllPrim()")
+        .isCustomFunction(FunctionProvider.nameBFCCollCTPrimCompRTESAllPrim);
+
+    parseEx("ESBaseTwoKeyNav", "BFCESBaseTwoKeyNavRTESBaseTwoKey()")
+        .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
+    parseEx("ESBaseTwoKeyNav", "Namespace1_Alias.BFCETBaseTwoKeyNavRTETTwoKeyNav()")
+        .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND);
+    parseEx("ESBaseTwoKeyNav", "Namespace1_Alias.BFCCollStringRTESTwoKeyNav()")
+        .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND);
+    parseEx("ESTwoKeyNav", "Namespace1_Alias.BFCESTwoKeyNavRTTwoKeyNav()")
+        .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_MUST_USE_COLLECTIONS);
+  }
+
+  @Test
+  public void groupBy() throws Exception {
+    parse("ESTwoKeyNav", "groupby((PropertyString))")
+        .is(GroupBy.class)
+        .goGroupBy(0).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+    parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16))")
+        .is(GroupBy.class)
+        .goGroupBy(0).goPath().first().isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false)
+        .n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16,PropertyString))")
+        .is(GroupBy.class)
+        .goGroupBy(1).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+    parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString))");
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString,PropertyString))")
+        .goGroupBy(2).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+  }
+
+  @Test
+  public void groupByAggregate() throws Exception {
+    parse("ESTwoKeyNav", "groupby((PropertyInt16),aggregate(PropertyInt16 with sum as s))")
+        .goGroupByOption().goAggregate(0).isStandardMethod(StandardMethod.SUM)
+        .goExpression().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyInt16),aggregate(PropertyInt16 with average as a))")
+        .goGroupByOption().goAggregate(0).isStandardMethod(StandardMethod.AVERAGE)
+        .goUp().goUp().goGroupBy(0).goPath()
+        .first().isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false)
+        .n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
+    parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16),"
+        + "aggregate(PropertyInt16 with sum as s,PropertyInt16 with average as a))")
+        .goGroupByOption().goAggregate(1).isStandardMethod(StandardMethod.AVERAGE);
+    parse("ESTwoKeyNav", "groupby((PropertyInt16),aggregate(NavPropertyETKeyNavMany/$count as c))")
+        .goGroupByOption().goAggregate(0).goPath().at(1).isCount();
+    parse("ESTwoKeyNav", "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
+        .goGroupByOption().goAggregate(0).goInlineAggregateExpression().isStandardMethod(StandardMethod.SUM)
+        .goUpAggregate().goPath().first()
+        .isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+        + "aggregate(PropertyInt16 with sum as s))")
+        .goGroupBy(1).goPath().at(1).isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+        + "aggregate(PropertyInt16 with sum as s from NavPropertyETKeyNavOne/PropertyInt16 with average))")
+        .goGroupByOption().goAggregate(0).goFrom(0).isStandardMethod(StandardMethod.AVERAGE);
+    parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne),aggregate(CollPropertyComp(PropertyInt16 with sum as s)))");
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETTwoKeyNavOne/PropertyInt16),"
+        + "topcount(2,PropertyInt16)/aggregate(PropertyInt16 with sum as s))")
+        .goGroupByOption()
+        .at(0).goBottomTop().isMethod(Method.TOP_COUNT)
+        .goUp().at(1).goAggregate(0).isStandardMethod(StandardMethod.SUM);
+  }
+
+  @Test
+  public void groupByRollUp() throws Exception {
+    parse("ESTwoKeyNav",
+        "groupby((rollup(NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+        + "rollup(NavPropertyETKeyNavOne/NavPropertyETTwoKeyNavOne/PropertyInt16,"
+        + "NavPropertyETTwoKeyNavOne/PropertyString),NavPropertyETTwoKeyNavOne/PropertyInt16),"
+        + "aggregate(PropertyInt16 with sum as s))")
+        .goGroupBy(1).goRollup(1).goPath()
+        .first().isNavProperty("NavPropertyETTwoKeyNavOne", EntityTypeProvider.nameETTwoKeyNav, false)
+        .n().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+
+    parse("ESTwoKeyNav",
+        "groupby((rollup($all,NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+        + "NavPropertyETTwoKeyNavOne/PropertyString),"
+        + "aggregate(PropertyInt16 with sum as s from NavPropertyETTwoKeyNavOne/PropertyInt16 with average "
+        + "from NavPropertyETTwoKeyNavOne/PropertyString with average))")
+        .goGroupBy(0).isRollupAll().goUp().goGroupByOption().goAggregate(0).goFrom(1)
+        .isStandardMethod(StandardMethod.AVERAGE).goExpression().goPath().at(1)
+        .isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
+  }
+
+  @Test
+  public void groupBySpecial() throws Exception {
+    parse("ESTwoKeyNav", "groupby((NavPropertyETTwoKeyNavOne/PropertyInt16),aggregate(customAggregate))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(0)
+        .goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty);
+
+    parse("ESTwoKeyNav",
+        "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany/$count as c,"
+        + "NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(0).isAlias("c").goPath().at(1).isCount();
+    parse("ESTwoKeyNav",
+        "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany($count as c),"
+        + "NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(0).goInlineAggregateExpression()
+        .isAlias("c").goPath().first().isCount();
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyString),"
+        + "aggregate(PropertyInt16 with sum as s,customAggregate))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(1).isStandardMethod(null).isAlias(null)
+        .goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty);
+    parse("ESTwoKeyNav",
+        "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s),"
+        + "NavPropertyETKeyNavMany/customAggregate))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(1)
+        .goPath().at(1).isUriPathInfoKind(UriResourceKind.primitiveProperty);
+    parse("ESTwoKeyNav",
+        "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s),"
+        + "NavPropertyETKeyNavMany(PropertyInt16 with average as a)))")
+        .is(GroupBy.class)
+        .goGroupByOption().goAggregate(0).goInlineAggregateExpression()
+        .isStandardMethod(StandardMethod.SUM).isAlias("s")
+        .goUpAggregate().goUp().goAggregate(1).goInlineAggregateExpression()
+        .isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
+  }
+
+  @Test
+  public void sequence() throws Exception {
+    parse("ESTwoKeyNav", "identity/identity/identity")
+        .at(0).is(Identity.class).at(1).is(Identity.class).at(2).is(Identity.class);
+
+    parse("ESTwoKeyNav", "filter(PropertyInt16 le 1)/aggregate(PropertyInt16 with sum as s)")
+        .at(0).is(Filter.class)
+        .at(1).is(Aggregate.class).goAggregate(0).isStandardMethod(StandardMethod.SUM).isAlias("s");
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne),aggregate(PropertyInt16 with sum as s))/"
+        + "aggregate(s with average as a)")
+        .at(1).goAggregate(0).isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
+    parse("ESTwoKeyNav",
+        "filter(PropertyInt16 ge 1)/"
+        + "groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))")
+        .at(0).is(Filter.class)
+        .at(1).is(GroupBy.class);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+        + "filter(s ge 10)/concat(identity,groupby((NavPropertyETKeyNavOne/PropertyString),"
+        + "aggregate(s with sum as t)))")
+        .at(0).is(GroupBy.class)
+        .at(1).is(Filter.class)
+        .at(2).is(Concat.class).goConcat(0).is(Identity.class);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+        + "filter(s ge 10)/groupby((rollup(NavPropertyETKeyNavOne/PropertyString,"
+        + "NavPropertyETKeyNavOne/PropertyCompAllPrim/PropertyDuration)),aggregate(s with sum as t))")
+        .at(0).is(GroupBy.class)
+        .at(1).is(Filter.class)
+        .at(2).is(GroupBy.class).goGroupBy(0).goRollup(1).goPath().at(2)
+        .isPrimitiveProperty("PropertyDuration", PropertyProvider.nameDuration, false);
+    parse("ESTwoKeyNav",
+        "groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+        + "concat(filter(s ge 10),groupby((NavPropertyETKeyNavOne/PropertyString),"
+        + "aggregate(s with sum as t)))")
+        .at(0).is(GroupBy.class)
+        .at(1).is(Concat.class).goConcat(1).is(GroupBy.class);
+
+    parse("ESTwoKeyNav",
+        "filter(PropertyInt16 eq 1)/expand(NavPropertyETKeyNavMany,filter(not PropertyCompAllPrim/PropertyBoolean))/"
+        + "groupby((NavPropertyETKeyNavOne/PropertyInt16),"
+        + "aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
+        .at(0).is(Filter.class)
+        .at(1).is(Expand.class)
+        .at(2).is(GroupBy.class);
+  }
+
+  private ApplyValidator parse(final String path, final String apply)
+      throws UriParserException, UriValidationException {
+    final UriInfo uriInfo = new Parser(edm, odata).parseUri(path, "$apply=" + apply, null);
+    return new ApplyValidator(uriInfo.getApplyOption());
+  }
+
+  private TestUriValidator parseEx(final String path, final String apply) {
+    return new TestUriValidator().setEdm(edm).runEx(path, "$apply=" + apply);
+  }
+
+  private final class ApplyValidator implements TestValidator {
+
+    private final ApplyOption applyOption;
+    private final ApplyValidator previous;
+    private ApplyItem applyItem;
+
+    protected ApplyValidator(final ApplyOption applyOption) {
+      this(applyOption, null);
+    }
+
+    private ApplyValidator(final ApplyOption applyOption, final ApplyValidator previous) {
+      this.applyOption = applyOption;
+      this.previous = previous;
+      at(0);
+    }
+
+    public ApplyValidator at(final int index) {
+      assertTrue(index < applyOption.getApplyItems().size());
+      applyItem = applyOption.getApplyItems().get(index);
+      return this;
+    }
+
+    public ApplyValidator is(final Class<? extends ApplyItem> cls) {
+      assertNotNull(applyItem);
+      assertTrue(cls.isAssignableFrom(applyItem.getClass()));
+      return this;
+    }
+
+    public AggregateValidator goAggregate(final int index) {
+      is(Aggregate.class);
+      assertTrue(index < ((Aggregate) applyItem).getExpressions().size());
+      return new AggregateValidator(((Aggregate) applyItem).getExpressions().get(index), this);
+    }
+
+    public ExpandValidator goExpand() {
+      is(Expand.class);
+      return new ExpandValidator().setUpValidator(this).setExpand(((Expand) applyItem).getExpandOption());
+    }
+
+    public FilterValidator goFilter() {
+      is(Filter.class);
+      return new FilterValidator().setFilter(((Filter) applyItem).getFilterOption());
+    }
+
+    public BottomTopValidator goBottomTop() {
+      is(BottomTop.class);
+      return new BottomTopValidator((BottomTop) applyItem, this);
+    }
+
+    public ApplyValidator isCustomFunction(final FullQualifiedName function) {
+      is(CustomFunction.class);
+      assertEquals(function, ((CustomFunction) applyItem).getFunction().getFullQualifiedName());
+      return this;
+    }
+
+    public ApplyValidator isSearch(final String serializedSearch) {
+      is(Search.class);
+      assertEquals(serializedSearch, ((Search) applyItem).getSearchOption().getSearchExpression().toString());
+      return this;
+    }
+
+    public ApplyValidator goConcat(final int index) {
+      is(Concat.class);
+      assertTrue(index < ((Concat) applyItem).getApplyOptions().size());
+      return new ApplyValidator(((Concat) applyItem).getApplyOptions().get(index), this);
+    }
+
+    public ComputeValidator goCompute(final int index) {
+      is(Compute.class);
+      assertTrue(index < ((Compute) applyItem).getExpressions().size());
+      return new ComputeValidator(((Compute) applyItem).getExpressions().get(index), this);
+    }
+
+    public GroupByValidator goGroupBy(final int index) {
+      is(GroupBy.class);
+      assertTrue(index < ((GroupBy) applyItem).getGroupByItems().size());
+      return new GroupByValidator(((GroupBy) applyItem).getGroupByItems().get(index), this);
+    }
+
+    public ApplyValidator goGroupByOption() {
+      is(GroupBy.class);
+      assertNotNull(((GroupBy) applyItem).getApplyOption());
+      return new ApplyValidator(((GroupBy) applyItem).getApplyOption(), this);
+    }
+
+    public ApplyValidator goUp() {
+      return previous;
+    }
+  }
+
+  private final class AggregateValidator implements TestValidator {
+
+    private final AggregateExpression aggregateExpression;
+    private final TestValidator previous;
+
+    protected AggregateValidator(final AggregateExpression aggregateExpression, final TestValidator previous) {
+      this.aggregateExpression = aggregateExpression;
+      this.previous = previous;
+    }
+
+    public AggregateValidator isStandardMethod(final AggregateExpression.StandardMethod method) {
+      assertNotNull(aggregateExpression);
+      assertEquals(method, aggregateExpression.getStandardMethod());
+      return this;
+    }
+
+    public AggregateValidator isCustomMethod(final FullQualifiedName method) {
+      assertNotNull(aggregateExpression);
+      assertEquals(method, aggregateExpression.getCustomMethod());
+      return this;
+    }
+
+    public AggregateValidator isAlias(final String alias) {
+      assertNotNull(aggregateExpression);
+      assertEquals(alias, aggregateExpression.getAlias());
+      return this;
+    }
+
+    public FilterValidator goExpression() {
+      assertNotNull(aggregateExpression);
+      assertNotNull(aggregateExpression.getExpression());
+      return new FilterValidator().setValidator(this).setEdm(edm)
+          .setExpression(aggregateExpression.getExpression());
+    }
+
+    public ResourceValidator goPath() {
+      assertNotNull(aggregateExpression);
+      assertFalse(aggregateExpression.getPath().isEmpty());
+      UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
+      for (final UriResource segment : aggregateExpression.getPath()) {
+        resource.addResourcePart(segment);
+      }
+      return new ResourceValidator().setUpValidator(this).setEdm(edm).setUriInfoPath(resource);
+    }
+
+    public AggregateValidator goInlineAggregateExpression() {
+      return new AggregateValidator(aggregateExpression.getInlineAggregateExpression(), this);
+    }
+
+    public AggregateValidator goFrom(final int index) {
+      assertTrue(index < aggregateExpression.getFrom().size());
+      return new AggregateValidator(aggregateExpression.getFrom().get(index), this);
+    }
+
+    public AggregateValidator goUpAggregate() {
+      return (AggregateValidator) previous;
+    }
+
+    public ApplyValidator goUp() {
+      return (ApplyValidator) previous;
+    }
+  }
+
+  private final class BottomTopValidator implements TestValidator {
+
+    private final BottomTop item;
+    private final ApplyValidator previous;
+
+    private BottomTopValidator(final BottomTop item, final ApplyValidator previous) {
+      this.item = item;
+      this.previous = previous;
+    }
+
+    public BottomTopValidator isMethod(final BottomTop.Method method) {
+      assertEquals(method, item.getMethod());
+      return this;
+    }
+
+    public FilterValidator goNumber() {
+      assertNotNull(item.getNumber());
+      return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getNumber());
+    }
+
+    public FilterValidator goValue() {
+      assertNotNull(item.getValue());
+      return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getValue());
+    }
+
+    public ApplyValidator goUp() {
+      return previous;
+    }
+  }
+
+  private final class ComputeValidator implements TestValidator {
+
+    private final ComputeExpression item;
+    private final ApplyValidator previous;
+
+    private ComputeValidator(final ComputeExpression item, final ApplyValidator previous) {
+      this.item = item;
+      this.previous = previous;
+    }
+
+    public ComputeValidator isAlias(final String alias) {
+      assertEquals(alias, item.getAlias());
+      return this;
+    }
+
+    public FilterValidator goExpression() {
+      assertNotNull(item.getExpression());
+      return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getExpression());
+    }
+
+    public ApplyValidator goUp() {
+      return previous;
+    }
+  }
+
+  private final class GroupByValidator implements TestValidator {
+
+    private final GroupByItem item;
+    private final TestValidator previous;
+
+    private GroupByValidator(final GroupByItem item, final TestValidator previous) {
+      this.item = item;
+      this.previous = previous;
+    }
+
+    public ResourceValidator goPath() {
+      assertFalse(item.getPath().isEmpty());
+      UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
+      for (final UriResource segment : item.getPath()) {
+        resource.addResourcePart(segment);
+      }
+      return new ResourceValidator().setUpValidator(this).setEdm(edm).setUriInfoPath(resource);
+    }
+
+    public GroupByValidator isRollupAll() {
+      assertTrue(item.isRollupAll());
+      return this;
+    }
+
+    public GroupByValidator goRollup(final int index) {
+      assertTrue(index < item.getRollup().size());
+      return new GroupByValidator(item.getRollup().get(index), this);
+    }
+
+    public ApplyValidator goUp() {
+      return (ApplyValidator) previous;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java
----------------------------------------------------------------------
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java
index f64a711..789e65f 100644
--- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java
@@ -88,16 +88,17 @@ public class UriValidatorTest {
   private static final String QO_SKIP = "$skip=3";
   private static final String QO_SKIPTOKEN = "$skiptoken=123";
   private static final String QO_TOP = "$top=1";
+  private static final String QO_APPLY = "$apply=identity";
 
   private final String[][] urisWithValidSystemQueryOptions = {
       { URI_ALL, QO_FILTER }, { URI_ALL, QO_FORMAT }, { URI_ALL, QO_EXPAND }, { URI_ALL, QO_COUNT },
       { URI_ALL, QO_ORDERBY }, { URI_ALL, QO_SEARCH }, { URI_ALL, QO_SELECT }, { URI_ALL, QO_SKIP },
-      { URI_ALL, QO_SKIPTOKEN }, { URI_ALL, QO_TOP },
+      { URI_ALL, QO_SKIPTOKEN }, { URI_ALL, QO_TOP }, { URI_ALL, QO_APPLY },
 
       { URI_CROSSJOIN, QO_FILTER }, { URI_CROSSJOIN, QO_FORMAT },
       { URI_CROSSJOIN, QO_EXPAND }, { URI_CROSSJOIN, QO_COUNT }, { URI_CROSSJOIN, QO_ORDERBY },
       { URI_CROSSJOIN, QO_SEARCH }, { URI_CROSSJOIN, QO_SELECT }, { URI_CROSSJOIN, QO_SKIP },
-      { URI_CROSSJOIN, QO_SKIPTOKEN }, { URI_CROSSJOIN, QO_TOP },
+      { URI_CROSSJOIN, QO_SKIPTOKEN }, { URI_CROSSJOIN, QO_TOP }, { URI_CROSSJOIN, QO_APPLY },
 
       { URI_ENTITY_ID, QO_ID, QO_FORMAT }, { URI_ENTITY_ID, QO_ID }, { URI_ENTITY_ID, QO_ID, QO_EXPAND },
       { URI_ENTITY_ID, QO_ID, QO_SELECT },
@@ -109,7 +110,7 @@ public class UriValidatorTest {
       { URI_ENTITY_SET, QO_FILTER }, { URI_ENTITY_SET, QO_FORMAT }, { URI_ENTITY_SET, QO_EXPAND },
       { URI_ENTITY_SET, QO_COUNT }, { URI_ENTITY_SET, QO_ORDERBY }, { URI_ENTITY_SET, QO_SEARCH },
       { URI_ENTITY_SET, QO_SELECT }, { URI_ENTITY_SET, QO_SKIP }, { URI_ENTITY_SET, QO_SKIPTOKEN },
-      { URI_ENTITY_SET, QO_TOP },
+      { URI_ENTITY_SET, QO_TOP }, { URI_ENTITY_SET, QO_APPLY },
 
       { URI_ENTITY_SET_COUNT, QO_FILTER }, { URI_ENTITY_SET_COUNT, QO_SEARCH },
 
@@ -127,7 +128,7 @@ public class UriValidatorTest {
       { URI_PROPERTY_COMPLEX_COLLECTION, QO_EXPAND }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_COUNT },
       { URI_PROPERTY_COMPLEX_COLLECTION, QO_ORDERBY }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SELECT },
       { URI_PROPERTY_COMPLEX_COLLECTION, QO_SKIP }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SKIPTOKEN },
-      { URI_PROPERTY_COMPLEX_COLLECTION, QO_TOP },
+      { URI_PROPERTY_COMPLEX_COLLECTION, QO_TOP }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_APPLY },
 
       { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_FILTER },
 
@@ -149,12 +150,12 @@ public class UriValidatorTest {
       { URI_NAV_ENTITY_SET, QO_FILTER }, { URI_NAV_ENTITY_SET, QO_FORMAT }, { URI_NAV_ENTITY_SET, QO_EXPAND },
       { URI_NAV_ENTITY_SET, QO_COUNT }, { URI_NAV_ENTITY_SET, QO_ORDERBY },
       { URI_NAV_ENTITY_SET, QO_SEARCH }, { URI_NAV_ENTITY_SET, QO_SELECT }, { URI_NAV_ENTITY_SET, QO_SKIP },
-      { URI_NAV_ENTITY_SET, QO_SKIPTOKEN }, { URI_NAV_ENTITY_SET, QO_TOP },
+      { URI_NAV_ENTITY_SET, QO_SKIPTOKEN }, { URI_NAV_ENTITY_SET, QO_TOP }, { URI_NAV_ENTITY_SET, QO_APPLY },
 
       { URI_FI_ENTITY_SET, QO_FILTER }, { URI_FI_ENTITY_SET, QO_FORMAT }, { URI_FI_ENTITY_SET, QO_EXPAND },
       { URI_FI_ENTITY_SET, QO_COUNT }, { URI_FI_ENTITY_SET, QO_ORDERBY }, { URI_FI_ENTITY_SET, QO_SEARCH },
       { URI_FI_ENTITY_SET, QO_SELECT }, { URI_FI_ENTITY_SET, QO_SKIP }, { URI_FI_ENTITY_SET, QO_SKIPTOKEN },
-      { URI_FI_ENTITY_SET, QO_TOP },
+      { URI_FI_ENTITY_SET, QO_TOP }, { URI_FI_ENTITY_SET, QO_APPLY },
 
       { URI_FI_ENTITY, QO_FORMAT }, { URI_FI_ENTITY, QO_EXPAND }, { URI_FI_ENTITY, QO_SELECT },
       { URI_FI_ENTITY_SET_KEY, QO_FORMAT }, { URI_FI_ENTITY_SET_KEY, QO_EXPAND }, { URI_FI_ENTITY_SET_KEY, QO_SELECT },
@@ -170,22 +171,23 @@ public class UriValidatorTest {
 
       { URI_BATCH, QO_FILTER }, { URI_BATCH, QO_FORMAT }, { URI_BATCH, QO_ID }, { URI_BATCH, QO_EXPAND },
       { URI_BATCH, QO_COUNT }, { URI_BATCH, QO_ORDERBY }, { URI_BATCH, QO_SEARCH }, { URI_BATCH, QO_SELECT },
-      { URI_BATCH, QO_SKIP }, { URI_BATCH, QO_SKIPTOKEN }, { URI_BATCH, QO_TOP },
+      { URI_BATCH, QO_SKIP }, { URI_BATCH, QO_SKIPTOKEN }, { URI_BATCH, QO_TOP }, { URI_BATCH, QO_APPLY },
 
       { URI_CROSSJOIN, QO_ID },
 
       { URI_ENTITY_ID, QO_ID, QO_FILTER },
-      { URI_ENTITY_ID, QO_ID, QO_COUNT }, { URI_ENTITY_ID, QO_ORDERBY }, { URI_ENTITY_ID, QO_SEARCH },
+      { URI_ENTITY_ID, QO_ID, QO_COUNT }, { URI_ENTITY_ID, QO_ID, QO_ORDERBY }, { URI_ENTITY_ID, QO_ID, QO_SEARCH },
       { URI_ENTITY_ID, QO_ID, QO_SKIP }, { URI_ENTITY_ID, QO_ID, QO_SKIPTOKEN }, { URI_ENTITY_ID, QO_ID, QO_TOP },
+      { URI_ENTITY_ID, QO_ID, QO_APPLY },
 
       { URI_METADATA, QO_FILTER }, { URI_METADATA, QO_ID }, { URI_METADATA, QO_EXPAND },
       { URI_METADATA, QO_COUNT }, { URI_METADATA, QO_ORDERBY }, { URI_METADATA, QO_SEARCH },
       { URI_METADATA, QO_SELECT }, { URI_METADATA, QO_SKIP }, { URI_METADATA, QO_SKIPTOKEN },
-      { URI_METADATA, QO_TOP },
+      { URI_METADATA, QO_TOP }, { URI_METADATA, QO_APPLY },
 
       { URI_SERVICE, QO_FILTER }, { URI_SERVICE, QO_ID }, { URI_SERVICE, QO_EXPAND }, { URI_SERVICE, QO_COUNT },
       { URI_SERVICE, QO_ORDERBY }, { URI_SERVICE, QO_SEARCH }, { URI_SERVICE, QO_SELECT },
-      { URI_SERVICE, QO_SKIP }, { URI_SERVICE, QO_SKIPTOKEN }, { URI_SERVICE, QO_TOP },
+      { URI_SERVICE, QO_SKIP }, { URI_SERVICE, QO_SKIPTOKEN }, { URI_SERVICE, QO_TOP }, { URI_SERVICE, QO_APPLY },
 
       { URI_ENTITY_SET, QO_ID },
 
@@ -193,26 +195,29 @@ public class UriValidatorTest {
       { URI_ENTITY_SET_COUNT, QO_EXPAND }, { URI_ENTITY_SET_COUNT, QO_COUNT },
       { URI_ENTITY_SET_COUNT, QO_ORDERBY },
       { URI_ENTITY_SET_COUNT, QO_SELECT }, { URI_ENTITY_SET_COUNT, QO_SKIP }, { URI_ENTITY_SET_COUNT, QO_SKIPTOKEN },
-      { URI_ENTITY_SET_COUNT, QO_TOP },
+      { URI_ENTITY_SET_COUNT, QO_TOP }, { URI_ENTITY_SET_COUNT, QO_APPLY },
 
       { URI_ENTITY, QO_FILTER }, { URI_ENTITY, QO_ID }, { URI_ENTITY, QO_COUNT }, { URI_ENTITY, QO_ORDERBY },
       { URI_ENTITY, QO_SEARCH }, { URI_ENTITY, QO_SKIP }, { URI_ENTITY, QO_SKIPTOKEN }, { URI_ENTITY, QO_TOP },
+      { URI_ENTITY, QO_APPLY },
 
       { URI_MEDIA_STREAM, QO_FILTER }, { URI_MEDIA_STREAM, QO_FORMAT }, { URI_MEDIA_STREAM, QO_ID },
       { URI_MEDIA_STREAM, QO_EXPAND }, { URI_MEDIA_STREAM, QO_COUNT }, { URI_MEDIA_STREAM, QO_ORDERBY },
       { URI_MEDIA_STREAM, QO_SEARCH }, { URI_MEDIA_STREAM, QO_SELECT }, { URI_MEDIA_STREAM, QO_SKIP },
-      { URI_MEDIA_STREAM, QO_SKIPTOKEN }, { URI_MEDIA_STREAM, QO_TOP },
+      { URI_MEDIA_STREAM, QO_SKIPTOKEN }, { URI_MEDIA_STREAM, QO_TOP }, { URI_MEDIA_STREAM, QO_APPLY },
 
       { URI_REFERENCES, QO_ID }, { URI_REFERENCES, QO_EXPAND }, { URI_REFERENCES, QO_SELECT },
+      { URI_REFERENCES, QO_APPLY },
 
       { URI_REFERENCE, QO_FILTER }, { URI_REFERENCE, QO_ID }, { URI_REFERENCE, QO_EXPAND },
       { URI_REFERENCE, QO_COUNT }, { URI_REFERENCE, QO_ORDERBY }, { URI_REFERENCE, QO_SEARCH },
       { URI_REFERENCE, QO_SELECT }, { URI_REFERENCE, QO_SKIP }, { URI_REFERENCE, QO_SKIPTOKEN },
-      { URI_REFERENCE, QO_TOP },
+      { URI_REFERENCE, QO_TOP }, { URI_REFERENCE, QO_APPLY },
 
       { URI_PROPERTY_COMPLEX, QO_FILTER }, { URI_PROPERTY_COMPLEX, QO_ID }, { URI_PROPERTY_COMPLEX, QO_COUNT },
       { URI_PROPERTY_COMPLEX, QO_ORDERBY }, { URI_PROPERTY_COMPLEX, QO_SEARCH },
       { URI_PROPERTY_COMPLEX, QO_SKIP }, { URI_PROPERTY_COMPLEX, QO_SKIPTOKEN }, { URI_PROPERTY_COMPLEX, QO_TOP },
+      { URI_PROPERTY_COMPLEX, QO_APPLY },
 
       { URI_PROPERTY_COMPLEX_COLLECTION, QO_ID }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SEARCH },
 
@@ -221,16 +226,17 @@ public class UriValidatorTest {
       { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_COUNT }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_ORDERBY },
       { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SEARCH }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SELECT },
       { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SKIP }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SKIPTOKEN },
-      { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_TOP },
+      { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_TOP }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_APPLY },
 
       { URI_PROPERTY_PRIMITIVE, QO_FILTER }, { URI_PROPERTY_PRIMITIVE, QO_ID }, { URI_PROPERTY_PRIMITIVE, QO_EXPAND },
       { URI_PROPERTY_PRIMITIVE, QO_COUNT }, { URI_PROPERTY_PRIMITIVE, QO_ORDERBY },
       { URI_PROPERTY_PRIMITIVE, QO_SEARCH }, { URI_PROPERTY_PRIMITIVE, QO_SELECT },
       { URI_PROPERTY_PRIMITIVE, QO_SKIP }, { URI_PROPERTY_PRIMITIVE, QO_SKIPTOKEN },
-      { URI_PROPERTY_PRIMITIVE, QO_TOP },
+      { URI_PROPERTY_PRIMITIVE, QO_TOP }, { URI_PROPERTY_PRIMITIVE, QO_APPLY },
 
       { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_ID }, { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_EXPAND },
       { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_SEARCH }, { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_SELECT },
+      { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_APPLY },
 
       { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_FORMAT },
       { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_ID }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_EXPAND },
@@ -238,20 +244,22 @@ public class UriValidatorTest {
       { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_ORDERBY }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SEARCH },
       { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SELECT }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SKIP },
       { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SKIPTOKEN }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_TOP },
+      { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_APPLY },
 
       { URI_PROPERTY_PRIMITIVE_VALUE, QO_FILTER }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_ID },
       { URI_PROPERTY_PRIMITIVE_VALUE, QO_EXPAND }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_COUNT },
       { URI_PROPERTY_PRIMITIVE_VALUE, QO_ORDERBY }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_SEARCH },
       { URI_PROPERTY_PRIMITIVE_VALUE, QO_SELECT }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_SKIP },
       { URI_PROPERTY_PRIMITIVE_VALUE, QO_SKIPTOKEN }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_TOP },
+      { URI_PROPERTY_PRIMITIVE_VALUE, QO_APPLY },
 
       { URI_SINGLETON, QO_FILTER }, { URI_SINGLETON, QO_ID }, { URI_SINGLETON, QO_COUNT },
       { URI_SINGLETON, QO_ORDERBY }, { URI_SINGLETON, QO_SEARCH }, { URI_SINGLETON, QO_SKIP },
-      { URI_SINGLETON, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP },
+      { URI_SINGLETON, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP }, { URI_SINGLETON, QO_APPLY },
 
       { URI_NAV_ENTITY, QO_FILTER }, { URI_NAV_ENTITY, QO_ID }, { URI_NAV_ENTITY, QO_COUNT },
       { URI_NAV_ENTITY, QO_ORDERBY }, { URI_NAV_ENTITY, QO_SEARCH }, { URI_NAV_ENTITY, QO_SKIP },
-      { URI_NAV_ENTITY, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP },
+      { URI_NAV_ENTITY, QO_SKIPTOKEN }, { URI_NAV_ENTITY, QO_TOP }, { URI_NAV_ENTITY, QO_APPLY },
 
       { URI_NAV_ENTITY_SET, QO_ID },
 
@@ -262,7 +270,8 @@ public class UriValidatorTest {
       { URI_FI_ENTITY, QO_SKIPTOKEN }, { URI_FI_ENTITY, QO_TOP },
       { URI_FI_ENTITY_SET_KEY, QO_FILTER }, { URI_FI_ENTITY_SET_KEY, QO_ID }, { URI_FI_ENTITY_SET_KEY, QO_COUNT },
       { URI_FI_ENTITY_SET_KEY, QO_ORDERBY }, { URI_FI_ENTITY_SET_KEY, QO_SEARCH },
-      { URI_FI_ENTITY_SET_KEY, QO_SKIP }, { URI_FI_ENTITY_SET_KEY, QO_SKIPTOKEN }, { URI_FI_ENTITY_SET_KEY, QO_TOP }
+      { URI_FI_ENTITY_SET_KEY, QO_SKIP }, { URI_FI_ENTITY_SET_KEY, QO_SKIPTOKEN }, { URI_FI_ENTITY_SET_KEY, QO_TOP },
+      { URI_FI_ENTITY_SET_KEY, QO_APPLY }
   };
 
   private final String[][] actionWithValidSystemQueryOptions = {
@@ -282,14 +291,14 @@ public class UriValidatorTest {
       { URI_ACTION_COLL_CT, QO_EXPAND }, { URI_ACTION_COLL_CT, QO_COUNT },
       { URI_ACTION_COLL_CT, QO_ORDERBY }, { URI_ACTION_COLL_CT, QO_SELECT },
       { URI_ACTION_COLL_CT, QO_SKIP }, { URI_ACTION_COLL_CT, QO_SKIPTOKEN },
-      { URI_ACTION_COLL_CT, QO_TOP },
+      { URI_ACTION_COLL_CT, QO_TOP }, { URI_ACTION_COLL_CT, QO_APPLY },
       // EntityReturnType
       { URI_ACTION_ENTITY, QO_FORMAT }, { URI_ACTION_ENTITY, QO_EXPAND }, { URI_ACTION_ENTITY, QO_SELECT },
       // EntityCollectionReturnType
       { URI_ACTION_ES, QO_FORMAT }, { URI_ACTION_ES, QO_FILTER },
       { URI_ACTION_ES, QO_COUNT }, { URI_ACTION_ES, QO_ORDERBY }, { URI_ACTION_ES, QO_SEARCH },
       { URI_ACTION_ES, QO_SELECT }, { URI_ACTION_ES, QO_SKIP }, { URI_ACTION_ES, QO_SKIPTOKEN },
-      { URI_ACTION_ES, QO_TOP }
+      { URI_ACTION_ES, QO_TOP }, { URI_ACTION_ES, QO_APPLY }
   };
 
   private final String[][] actionsWithNotValidSystemQueryOptions = {
@@ -298,26 +307,28 @@ public class UriValidatorTest {
       { URI_ACTION_NO_RETURN, QO_EXPAND }, { URI_ACTION_NO_RETURN, QO_COUNT },
       { URI_ACTION_NO_RETURN, QO_ORDERBY }, { URI_ACTION_NO_RETURN, QO_SEARCH },
       { URI_ACTION_NO_RETURN, QO_SELECT }, { URI_ACTION_NO_RETURN, QO_SKIP },
-      { URI_ACTION_NO_RETURN, QO_SKIPTOKEN }, { URI_ACTION_NO_RETURN, QO_TOP },
+      { URI_ACTION_NO_RETURN, QO_SKIPTOKEN }, { URI_ACTION_NO_RETURN, QO_TOP }, { URI_ACTION_NO_RETURN, QO_APPLY },
       // PrimReturnType
       { URI_ACTION_PRIM, QO_FILTER }, { URI_ACTION_PRIM, QO_ID },
       { URI_ACTION_PRIM, QO_EXPAND }, { URI_ACTION_PRIM, QO_COUNT },
       { URI_ACTION_PRIM, QO_ORDERBY }, { URI_ACTION_PRIM, QO_SEARCH },
       { URI_ACTION_PRIM, QO_SELECT }, { URI_ACTION_PRIM, QO_SKIP },
-      { URI_ACTION_PRIM, QO_SKIPTOKEN }, { URI_ACTION_PRIM, QO_TOP },
+      { URI_ACTION_PRIM, QO_SKIPTOKEN }, { URI_ACTION_PRIM, QO_TOP }, { URI_ACTION_PRIM, QO_APPLY },
       // PrimCollectionReturnType
       { URI_ACTION_COLL_PRIM, QO_ID }, { URI_ACTION_COLL_PRIM, QO_EXPAND },
-      { URI_ACTION_COLL_PRIM, QO_SEARCH }, { URI_ACTION_COLL_PRIM, QO_SELECT },
+      { URI_ACTION_COLL_PRIM, QO_SEARCH }, { URI_ACTION_COLL_PRIM, QO_SELECT }, { URI_ACTION_COLL_PRIM, QO_APPLY },
       // ComplexReturnType
       { URI_ACTION_CT, QO_FILTER }, { URI_ACTION_CT, QO_ID }, { URI_ACTION_CT, QO_COUNT },
       { URI_ACTION_CT, QO_ORDERBY }, { URI_ACTION_CT, QO_SEARCH },
       { URI_ACTION_CT, QO_SKIP }, { URI_ACTION_CT, QO_SKIPTOKEN }, { URI_ACTION_CT, QO_TOP },
+      { URI_ACTION_CT, QO_APPLY },
       // ComplexCollectionReturnType
       { URI_ACTION_COLL_CT, QO_ID }, { URI_ACTION_COLL_CT, QO_SEARCH },
       // EntityReturnType
       { URI_ACTION_ENTITY, QO_FILTER }, { URI_ACTION_ENTITY, QO_ID }, { URI_ACTION_ENTITY, QO_COUNT },
       { URI_ACTION_ENTITY, QO_ORDERBY }, { URI_ACTION_ENTITY, QO_SEARCH },
       { URI_ACTION_ENTITY, QO_SKIP }, { URI_ACTION_ENTITY, QO_SKIPTOKEN }, { URI_ACTION_ENTITY, QO_TOP },
+      { URI_ACTION_ENTITY, QO_APPLY },
       // EntityCollectionReturnType
       { URI_ACTION_ES, QO_ID }
   };


[3/3] olingo-odata4 git commit: [OLINGO-935]URI-parser support of the data aggregation extension

Posted by ch...@apache.org.
[OLINGO-935]URI-parser support of the data aggregation extension

Signed-off-by: Christian Amend <ch...@sap.com>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/6553e950
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/6553e950
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/6553e950

Branch: refs/heads/master
Commit: 6553e95080b66b80f1e9e5e871f941d2164060c1
Parents: 824c174
Author: Klaus Straubinger <kl...@sap.com>
Authored: Fri Apr 15 16:11:30 2016 +0200
Committer: Christian Amend <ch...@sap.com>
Committed: Mon Apr 18 15:06:33 2016 +0200

----------------------------------------------------------------------
 .../olingo/server/api/uri/UriInfoResource.java  |   6 +
 .../server/api/uri/queryoption/ApplyItem.java   |  45 ++
 .../server/api/uri/queryoption/ApplyOption.java |  32 +
 .../uri/queryoption/SystemQueryOptionKind.java  |   7 +-
 .../api/uri/queryoption/apply/Aggregate.java    |  35 +
 .../queryoption/apply/AggregateExpression.java  |  80 +++
 .../api/uri/queryoption/apply/BottomTop.java    |  51 ++
 .../api/uri/queryoption/apply/Compute.java      |  35 +
 .../queryoption/apply/ComputeExpression.java    |  40 ++
 .../api/uri/queryoption/apply/Concat.java       |  36 +
 .../uri/queryoption/apply/CustomFunction.java   |  43 ++
 .../api/uri/queryoption/apply/Expand.java       |  34 +
 .../api/uri/queryoption/apply/Filter.java       |  34 +
 .../api/uri/queryoption/apply/GroupBy.java      |  42 ++
 .../api/uri/queryoption/apply/GroupByItem.java  |  48 ++
 .../api/uri/queryoption/apply/Identity.java     |  27 +
 .../api/uri/queryoption/apply/Search.java       |  34 +
 .../olingo/server/core/debug/DebugTabUri.java   | 191 +++++-
 .../core/debug/ExpressionJsonVisitor.java       |  54 +-
 .../olingo/server/core/uri/UriInfoImpl.java     |  12 +-
 .../server/core/uri/parser/ApplyParser.java     | 570 ++++++++++++++++
 .../olingo/server/core/uri/parser/Parser.java   |  31 +-
 .../uri/parser/UriParserSemanticException.java  |  12 +-
 .../server/core/uri/parser/UriTokenizer.java    | 135 +++-
 .../core/uri/queryoption/ApplyOptionImpl.java   |  46 ++
 .../apply/AggregateExpressionImpl.java          | 113 ++++
 .../uri/queryoption/apply/AggregateImpl.java    |  48 ++
 .../uri/queryoption/apply/BottomTopImpl.java    |  69 ++
 .../apply/ComputeExpressionImpl.java            |  51 ++
 .../core/uri/queryoption/apply/ComputeImpl.java |  48 ++
 .../core/uri/queryoption/apply/ConcatImpl.java  |  48 ++
 .../queryoption/apply/CustomFunctionImpl.java   |  62 ++
 .../uri/queryoption/apply/DynamicProperty.java  | 118 ++++
 .../apply/DynamicStructuredType.java            | 141 ++++
 .../core/uri/queryoption/apply/ExpandImpl.java  |  45 ++
 .../core/uri/queryoption/apply/FilterImpl.java  |  45 ++
 .../core/uri/queryoption/apply/GroupByImpl.java |  60 ++
 .../uri/queryoption/apply/GroupByItemImpl.java  |  67 ++
 .../uri/queryoption/apply/IdentityImpl.java     |  32 +
 .../core/uri/queryoption/apply/SearchImpl.java  |  45 ++
 .../server/core/uri/validator/UriValidator.java |  44 +-
 .../server-core-exceptions-i18n.properties      |   3 +
 .../core/uri/parser/UriTokenizerTest.java       |  70 ++
 .../tecsvc/processor/TechnicalProcessor.java    |   2 +-
 .../server/core/uri/parser/ApplyParserTest.java | 676 +++++++++++++++++++
 .../core/uri/validator/UriValidatorTest.java    |  57 +-
 46 files changed, 3416 insertions(+), 108 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
index 0550eae..05e010e 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
@@ -20,6 +20,7 @@ package org.apache.olingo.server.api.uri;
 
 import java.util.List;
 
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
 import org.apache.olingo.server.api.uri.queryoption.CountOption;
 import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption;
 import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -100,6 +101,11 @@ public interface UriInfoResource {
   TopOption getTopOption();
 
   /**
+   * @return information about the $apply option
+   */
+  ApplyOption getApplyOption();
+
+  /**
    * The path segments behind the service root define which resources are
    * requested by that URI. This may be entities/functions/actions and more.
    * Each segments information (name, key predicates, function parameters, ...) is

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
new file mode 100644
index 0000000..34588e2
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.api.uri.queryoption;
+
+/**
+ * Represents a single transformation from the system query option $apply.
+ */
+public interface ApplyItem {
+
+  /** The kind of the transformation. */
+  public enum Kind {
+    AGGREGATE,
+    BOTTOM_TOP,
+    COMPUTE,
+    CONCAT,
+    CUSTOM_FUNCTION,
+    EXPAND,
+    FILTER,
+    GROUP_BY,
+    IDENTITY,
+    SEARCH
+  }
+
+  /**
+   * Gets the kind of the transformation.
+   * @return transformation kind
+   */
+  Kind getKind();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
new file mode 100644
index 0000000..e5b10b5
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.api.uri.queryoption;
+
+import java.util.List;
+
+/**
+ * Represents the system query option $apply, defined in the data aggregation extension.
+ */
+public interface ApplyOption extends SystemQueryOption {
+
+  /**
+   * @return a list of transformations
+   */
+  List<ApplyItem> getApplyItems();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
index d5d60a1..3cfe47f 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
@@ -82,7 +82,12 @@ public enum SystemQueryOptionKind {
   /**
    * @see LevelsExpandOption
    */
-  LEVELS("$levels");
+  LEVELS("$levels"),
+
+  /**
+   * @see ApplyOption
+   */
+  APPLY("$apply");
 
   private final String syntax;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
new file mode 100644
index 0000000..d589aed
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the aggregate transformation.
+ */
+public interface Aggregate extends ApplyItem {
+
+  /**
+   * Gets the aggregate expressions.
+   * @return a non-empty list of aggregate expressions (and never <code>null</code>)
+   */
+  List<AggregateExpression> getExpressions();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
new file mode 100644
index 0000000..e297a22
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
@@ -0,0 +1,80 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents an aggregate expression.
+ * @see Aggregate
+ */
+public interface AggregateExpression {
+
+  /** Standard aggregation method. */
+  public enum StandardMethod { SUM, MIN, MAX, AVERAGE, COUNT_DISTINCT }
+
+  /**
+   * Gets the path prefix and the path segment.
+   * @return a (potentially empty) list of path segments (and never <code>null</code>)
+   */
+  List<UriResource> getPath();
+
+  /**
+   * Gets the common expression to be aggregated.
+   * @return an {@link Expression} that could be <code>null</code>
+   */
+  Expression getExpression();
+
+  /**
+   * Gets the standard aggregation method if used.
+   * @return a {@link StandardMethod} or <code>null</code>
+   * @see #getCustomMethod()
+   */
+  StandardMethod getStandardMethod();
+
+  /**
+   * Gets the name of the custom aggregation method if used.
+   * @return a {@link FullQualifiedName} or <code>null</code>
+   * @see #getStandardMethod()
+   */
+  FullQualifiedName getCustomMethod();
+
+  /**
+   * Gets the name of the aggregate if an alias name has been set.
+   * @return an identifier String or <code>null</code>
+   */
+  String getAlias();
+
+  /**
+   * Gets the inline aggregation expression to be applied to the target of the path if used.
+   * @return an aggregation expression or <code>null</code>
+   * @see #getPath()
+   */
+  AggregateExpression getInlineAggregateExpression();
+
+  /**
+   * Gets the aggregate expressions for <code>from</code>.
+   * @return a (potentially empty) list of aggregate expressions (but never <code>null</code>)
+   */
+  List<AggregateExpression> getFrom();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
new file mode 100644
index 0000000..5249c4b
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
@@ -0,0 +1,51 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents a transformation with one of the pre-defined methods
+ * <code>bottomcount</code>, <code>bottompercent</code>, <code>bottomsum</code>,
+ * <code>topcount</code>, <code>toppercent</code>, <code>topsum</code>.
+ */
+public interface BottomTop extends ApplyItem {
+
+  /** Pre-defined method for partial aggregration. */
+  public enum Method { BOTTOM_COUNT, BOTTOM_PERCENT, BOTTOM_SUM, TOP_COUNT, TOP_PERCENT, TOP_SUM }
+
+  /**
+   * Gets the partial-aggregation method.
+   * @return a {@link Method} (but never <code>null</code>)
+   */
+  Method getMethod();
+
+  /**
+   * Gets the expression that determines the number of items to aggregate.
+   * @return an {@link Expression} (but never <code>null</code>)
+   */
+  Expression getNumber();
+
+  /**
+   * Gets the expression that determines the values to aggregate.
+   * @return an {@link Expression} (but never <code>null</code>)
+   */
+  Expression getValue();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
new file mode 100644
index 0000000..addb503
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the compute transformation.
+ */
+public interface Compute extends ApplyItem {
+
+  /**
+   * Gets the compute expressions.
+   * @return a non-empty list of compute expressions (and never <code>null</code>)
+   */
+  List<ComputeExpression> getExpressions();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
new file mode 100644
index 0000000..c65bb83
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents a compute expression.
+ * @see Compute
+ */
+public interface ComputeExpression {
+
+  /**
+   * Gets the expression to compute.
+   * @return an {@link Expression} (but never <code>null</code>)
+   */
+  Expression getExpression();
+
+  /**
+   * Gets the name of the computation result if an alias name has been set.
+   * @return an identifier String (but never <code>null</code>)
+   */
+  String getAlias();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
new file mode 100644
index 0000000..d3fe6c8
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
@@ -0,0 +1,36 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+
+/**
+ * Represents the concat transformation.
+ */
+public interface Concat extends ApplyItem {
+
+  /**
+   * Gets the concatenated apply options.
+   * @return a non-empty list of apply options (and never <code>null</code>)
+   */
+  List<ApplyOption> getApplyOptions();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
new file mode 100644
index 0000000..d3db32f
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.EdmFunction;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents a transformation with a custom function.
+ */
+public interface CustomFunction extends ApplyItem {
+
+  /**
+   * Gets the function to use.
+   * @return an {@link EdmFunction} (but never <code>null</code>)
+   */
+  EdmFunction getFunction();
+
+  /**
+   * Gets the function parameters.
+   * @return a (potentially empty) list of parameters (but never <code>null</code>)
+   */
+  List<UriParameter> getParameters();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
new file mode 100644
index 0000000..9bab8ea
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
+
+/**
+ * Represents the expand transformation.
+ */
+public interface Expand extends ApplyItem {
+
+  /**
+   * Gets the expand option.
+   * @return an {@link ExpandOption} (but never <code>null</code>)
+   */
+  ExpandOption getExpandOption();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
new file mode 100644
index 0000000..2989950
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.FilterOption;
+
+/**
+ * Represents the filter transformation.
+ */
+public interface Filter extends ApplyItem {
+
+  /**
+   * Gets the filter option.
+   * @return a {@link FilterOption} (but never <code>null</code>)
+   */
+  FilterOption getFilterOption();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
new file mode 100644
index 0000000..5594b76
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+
+/**
+ * Represents the grouping transformation.
+ */
+public interface GroupBy extends ApplyItem {
+
+  /**
+   * Gets the items to group.
+   * @return a non-empty list of {@link GroupByItem}s (but never <code>null</code>)
+   */
+  List<GroupByItem> getGroupByItems();
+
+  /**
+   * Gets the apply option to be applied to the grouped items.
+   * @return an {@link ApplyOption} (but never <code>null</code>)
+   */
+  ApplyOption getApplyOption();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
new file mode 100644
index 0000000..f4738ec
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.UriResource;
+
+/**
+ * Represents a grouping property.
+ * @see GroupBy
+ */
+public interface GroupByItem {
+
+  /**
+   * Gets the path.
+   * @return a (potentially empty) list of path segments (and never <code>null</code>)
+   */
+  List<UriResource> getPath();
+
+  /**
+   * Whether a nested rollup clause contains the special value '$all'.
+   * @return <code>true</code> if '$all' has been given in rollup, <code>false</code> otherwise
+   */
+  boolean isRollupAll();
+
+  /**
+   * Gets the rollup.
+   * @return a (potentially empty) list of grouping items (and never <code>null</code>)
+   */
+  List<GroupByItem> getRollup();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
new file mode 100644
index 0000000..fbe050a
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the identity transformation.
+ */
+public interface Identity extends ApplyItem {
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
new file mode 100644
index 0000000..c0d8362
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+
+/**
+ * Represents the search transformation.
+ */
+public interface Search extends ApplyItem {
+
+  /**
+   * Gets the search option.
+   * @return a {@link SearchOption} (but never <code>null</code>)
+   */
+  SearchOption getSearchOption();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
index 842b8f5..05ac2aa 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
@@ -34,6 +34,8 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet;
 import org.apache.olingo.server.api.uri.UriResourceFunction;
 import org.apache.olingo.server.api.uri.UriResourceNavigation;
 import org.apache.olingo.server.api.uri.UriResourcePartTyped;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
 import org.apache.olingo.server.api.uri.queryoption.CountOption;
 import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
 import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -46,6 +48,18 @@ import org.apache.olingo.server.api.uri.queryoption.SelectItem;
 import org.apache.olingo.server.api.uri.queryoption.SelectOption;
 import org.apache.olingo.server.api.uri.queryoption.SkipOption;
 import org.apache.olingo.server.api.uri.queryoption.TopOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
+import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+import org.apache.olingo.server.api.uri.queryoption.apply.Search;
 import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
 import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
 
@@ -94,7 +108,7 @@ public class DebugTabUri implements DebugTab {
 
     appendCommonJsonObjects(gen, uriInfo.getCountOption(), uriInfo.getSkipOption(), uriInfo.getTopOption(),
         uriInfo.getFilterOption(), uriInfo.getOrderByOption(), uriInfo.getSelectOption(), uriInfo.getExpandOption(),
-        uriInfo.getSearchOption());
+        uriInfo.getSearchOption(), uriInfo.getApplyOption());
 
     if (!uriInfo.getAliases().isEmpty()) {
       gen.writeFieldName("aliases");
@@ -109,12 +123,12 @@ public class DebugTabUri implements DebugTab {
     gen.writeEndObject();
   }
 
-  private void appendCommonJsonObjects(final JsonGenerator gen, final CountOption countOption,
-      final SkipOption skipOption,
-      final TopOption topOption, final FilterOption filterOption, final OrderByOption orderByOption,
-      final SelectOption selectOption,
-      final ExpandOption expandOption, final SearchOption searchOption)
-          throws IOException {
+  private void appendCommonJsonObjects(JsonGenerator gen,
+      final CountOption countOption, final SkipOption skipOption, final TopOption topOption,
+      final FilterOption filterOption, final OrderByOption orderByOption,
+      final SelectOption selectOption, final ExpandOption expandOption, final SearchOption searchOption,
+      final ApplyOption applyOption)
+      throws IOException {
     if (countOption != null) {
       gen.writeBooleanField("isCount", countOption.getValue());
     }
@@ -155,6 +169,11 @@ public class DebugTabUri implements DebugTab {
       gen.writeFieldName("search");
       appendSearchJson(gen, searchOption.getSearchExpression());
     }
+
+    if (applyOption != null) {
+      gen.writeFieldName("apply");
+      appendApplyItemsJson(gen, applyOption.getApplyItems());
+    }
   }
 
   private void appendURIResourceParts(final JsonGenerator gen, final List<UriResource> uriResourceParts)
@@ -223,14 +242,7 @@ public class DebugTabUri implements DebugTab {
       gen.writeBooleanField("star", item.isStar());
     } else if (item.getResourcePath() != null && !item.getResourcePath().getUriResourceParts().isEmpty()) {
       gen.writeFieldName("expandPath");
-      gen.writeStartArray();
-      for (UriResource resource : item.getResourcePath().getUriResourceParts()) {
-        gen.writeStartObject();
-        gen.writeStringField("propertyKind", resource.getKind().toString());
-        gen.writeStringField("propertyName", resource.toString());
-        gen.writeEndObject();
-      }
-      gen.writeEndArray();
+      appendURIResourceParts(gen, item.getResourcePath().getUriResourceParts());
     }
 
     if (item.isRef()) {
@@ -240,7 +252,7 @@ public class DebugTabUri implements DebugTab {
     if (item.getLevelsOption() != null) {
       gen.writeFieldName("levels");
       if (item.getLevelsOption().isMax()) {
-          gen.writeString("max");
+        gen.writeString("max");
       } else {
         gen.writeNumber(item.getLevelsOption().getValue());
       }
@@ -248,7 +260,7 @@ public class DebugTabUri implements DebugTab {
 
     appendCommonJsonObjects(gen, item.getCountOption(), item.getSkipOption(), item.getTopOption(),
         item.getFilterOption(), item.getOrderByOption(), item.getSelectOption(), item.getExpandOption(),
-        item.getSearchOption());
+        item.getSearchOption(), null); // TODO: item.getApplyOption()
 
     gen.writeEndObject();
   }
@@ -315,6 +327,142 @@ public class DebugTabUri implements DebugTab {
     json.writeEndObject();
   }
 
+  private void appendApplyItemsJson(JsonGenerator json, final List<ApplyItem> applyItems) throws IOException {
+    json.writeStartArray();
+    for (final ApplyItem item : applyItems) {
+      appendApplyItemJson(json, item);
+    }
+    json.writeEndArray();
+  }
+
+  private void appendApplyItemJson(JsonGenerator json, final ApplyItem item) throws IOException {
+    json.writeStartObject();
+
+    json.writeStringField("kind", item.getKind().name());
+    switch (item.getKind()) {
+    case AGGREGATE:
+      appendAggregateJson(json, (Aggregate) item);
+      break;
+    case BOTTOM_TOP:
+      json.writeStringField("method", ((BottomTop) item).getMethod().name());
+      json.writeFieldName("number");
+      appendExpressionJson(json, ((BottomTop) item).getNumber());
+      json.writeFieldName("value");
+      appendExpressionJson(json, ((BottomTop) item).getValue());
+      break;
+    case COMPUTE:
+      json.writeFieldName("compute");
+      json.writeStartArray();
+      for (final ComputeExpression computeExpression : ((Compute) item).getExpressions()) {
+        json.writeStartObject();
+        json.writeFieldName("expression");
+        appendExpressionJson(json, computeExpression.getExpression());
+        json.writeStringField("as", computeExpression.getAlias());
+        json.writeEndObject();
+      }
+      json.writeEndArray();
+      break;
+    case CONCAT:
+      json.writeFieldName("concat");
+      json.writeStartArray();
+      for (final ApplyOption option : ((Concat) item).getApplyOptions()) {
+        appendApplyItemsJson(json, option.getApplyItems());
+      }
+      json.writeEndArray();
+      break;
+    case CUSTOM_FUNCTION:
+      json.writeStringField("name",
+          ((CustomFunction) item).getFunction().getFullQualifiedName().getFullQualifiedNameAsString());
+      appendParameters(json, "parameters", ((CustomFunction) item).getParameters());
+      break;
+    case EXPAND:
+      appendCommonJsonObjects(json, null, null, null, null, null, null, ((Expand) item).getExpandOption(), null, null);
+      break;
+    case FILTER:
+      appendCommonJsonObjects(json, null, null, null, ((Filter) item).getFilterOption(), null, null, null, null, null);
+      break;
+    case GROUP_BY:
+      json.writeFieldName("groupBy");
+      appendGroupByItemsJson(json, ((GroupBy) item).getGroupByItems());
+      appendCommonJsonObjects(json, null, null, null, null, null, null, null, null, ((GroupBy) item).getApplyOption());
+      break;
+    case IDENTITY:
+      break;
+    case SEARCH:
+      appendCommonJsonObjects(json, null, null, null, null, null, null, null, ((Search) item).getSearchOption(), null);
+      break;
+    }
+
+    json.writeEndObject();
+  }
+
+  private void appendGroupByItemsJson(JsonGenerator json, final List<GroupByItem> groupByItems) throws IOException {
+    json.writeStartArray();
+    for (final GroupByItem groupByItem : groupByItems) {
+      json.writeStartObject();
+      if (!groupByItem.getPath().isEmpty()) {
+        json.writeFieldName("path");
+        appendURIResourceParts(json, groupByItem.getPath());
+      }
+      json.writeBooleanField("isRollupAll", groupByItem.isRollupAll());
+      if (!groupByItem.getRollup().isEmpty()) {
+        json.writeFieldName("rollup");
+        appendGroupByItemsJson(json, groupByItem.getRollup());
+      }
+      json.writeEndObject();
+    }
+    json.writeEndArray();
+  }
+
+  private void appendAggregateJson(JsonGenerator json, final Aggregate aggregate) throws IOException {
+    json.writeFieldName("aggregate");
+    appendAggregateExpressionsJson(json, aggregate.getExpressions());
+  }
+
+  private void appendAggregateExpressionsJson(JsonGenerator json, final List<AggregateExpression> aggregateExpressions)
+      throws IOException {
+    json.writeStartArray();
+    for (final AggregateExpression aggregateExpression : aggregateExpressions) {
+      appendAggregateExpressionJson(json, aggregateExpression);
+    }
+    json.writeEndArray();
+  }
+
+  private void appendAggregateExpressionJson(JsonGenerator json, final AggregateExpression aggregateExpression)
+      throws IOException {
+    if (aggregateExpression == null) {
+      json.writeNull();
+    } else {
+      json.writeStartObject();
+      if (!aggregateExpression.getPath().isEmpty()) {
+        json.writeFieldName("path");
+        appendURIResourceParts(json, aggregateExpression.getPath());
+      }
+      if (aggregateExpression.getExpression() != null) {
+        json.writeFieldName("expression");
+        appendExpressionJson(json, aggregateExpression.getExpression());
+      }
+      if (aggregateExpression.getStandardMethod() != null) {
+        json.writeStringField("standardMethod", aggregateExpression.getStandardMethod().name());
+      }
+      if (aggregateExpression.getCustomMethod() != null) {
+        json.writeStringField("customMethod", aggregateExpression.getCustomMethod().getFullQualifiedNameAsString());
+      }
+      if (aggregateExpression.getAlias() != null) {
+        json.writeStringField("as", aggregateExpression.getAlias());
+      }
+      if (aggregateExpression.getInlineAggregateExpression() != null) {
+        json.writeFieldName("inlineAggregateExpression");
+        appendAggregateExpressionJson(json, aggregateExpression.getInlineAggregateExpression());
+      }
+      if (!aggregateExpression.getFrom().isEmpty()) {
+        json.writeFieldName("from");
+        appendAggregateExpressionsJson(json, aggregateExpression.getFrom());
+      }
+      json.writeEndObject();
+    }
+  }
+
   @Override
   public void appendHtml(final Writer writer) throws IOException {
     // factory for JSON generators (the object mapper is necessary to write expression trees)
@@ -377,6 +525,15 @@ public class DebugTabUri implements DebugTab {
       writer.append("</ul>\n");
     }
 
+    if (uriInfo.getApplyOption() != null) {
+      writer.append("<h2>Apply Option</h2>\n")
+          .append("<ul>\n<li class=\"json\">");
+      json = jsonFactory.createGenerator(writer).useDefaultPrettyPrinter();
+      appendApplyItemsJson(json, uriInfo.getApplyOption().getApplyItems());
+      json.close();
+      writer.append("\n</li>\n</ul>\n");
+    }
+
     if (uriInfo.getCountOption() != null
         || uriInfo.getSkipOption() != null
         || uriInfo.getSkipTokenOption() != null

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
index a8b22e9..b93bb3f 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
@@ -129,31 +129,28 @@ public class ExpressionJsonVisitor implements ExpressionVisitor<JsonNode> {
   public JsonNode visitMember(final Member member)
       throws ExpressionVisitException, ODataApplicationException {
     final List<UriResource> uriResourceParts = member.getResourcePath().getUriResourceParts();
+    final UriResource lastSegment = uriResourceParts.get(uriResourceParts.size() - 1);
     ObjectNode result = nodeFactory.objectNode()
         .put(NODE_TYPE_NAME, MEMBER_NAME)
-        .put(TYPE_NAME, getType(uriResourceParts));
+        .put(TYPE_NAME, getType(lastSegment));
     ArrayNode segments = result.putArray(RESOURCE_SEGMENTS_NAME);
-    if (uriResourceParts != null) {
-      for (final UriResource segment : uriResourceParts) {
-        if (segment instanceof UriResourceLambdaAll) {
-          final UriResourceLambdaAll all = (UriResourceLambdaAll) segment;
-          segments.add(visitLambdaExpression(ALL_NAME, all.getLambdaVariable(), all.getExpression()));
-        } else if (segment instanceof UriResourceLambdaAny) {
-          final UriResourceLambdaAny any = (UriResourceLambdaAny) segment;
-          segments.add(visitLambdaExpression(ANY_NAME, any.getLambdaVariable(), any.getExpression()));
-        } else if (segment instanceof UriResourcePartTyped) {
-          final String typeName = ((UriResourcePartTyped) segment).getType()
-              .getFullQualifiedName().getFullQualifiedNameAsString();
-          segments.add(nodeFactory.objectNode()
-              .put(NODE_TYPE_NAME, segment.getKind().toString())
-              .put(NAME_NAME, segment.toString())
-              .put(TYPE_NAME, typeName));
-        } else {
-          segments.add(nodeFactory.objectNode()
-              .put(NODE_TYPE_NAME, segment.getKind().toString())
-              .put(NAME_NAME, segment.toString())
-              .putNull(TYPE_NAME));
-        }
+    for (final UriResource segment : uriResourceParts) {
+      if (segment instanceof UriResourceLambdaAll) {
+        final UriResourceLambdaAll all = (UriResourceLambdaAll) segment;
+        segments.add(visitLambdaExpression(ALL_NAME, all.getLambdaVariable(), all.getExpression()));
+      } else if (segment instanceof UriResourceLambdaAny) {
+        final UriResourceLambdaAny any = (UriResourceLambdaAny) segment;
+        segments.add(visitLambdaExpression(ANY_NAME, any.getLambdaVariable(), any.getExpression()));
+      } else if (segment instanceof UriResourcePartTyped) {
+        segments.add(nodeFactory.objectNode()
+            .put(NODE_TYPE_NAME, segment.getKind().toString())
+            .put(NAME_NAME, segment.toString())
+            .put(TYPE_NAME, getType(segment)));
+      } else {
+        segments.add(nodeFactory.objectNode()
+            .put(NODE_TYPE_NAME, segment.getKind().toString())
+            .put(NAME_NAME, segment.toString())
+            .putNull(TYPE_NAME));
       }
     }
     return result;
@@ -287,15 +284,8 @@ public class ExpressionJsonVisitor implements ExpressionVisitor<JsonNode> {
     return type == null ? null : type.getFullQualifiedName().getFullQualifiedNameAsString();
   }
 
-  private String getType(final List<UriResource> uriResourceParts) {
-    if (uriResourceParts == null || uriResourceParts.isEmpty()) {
-      return null;
-    }
-    final UriResource lastSegment = uriResourceParts.get(uriResourceParts.size() - 1);
-    final EdmType type = lastSegment instanceof UriResourcePartTyped ?
-        ((UriResourcePartTyped) lastSegment).getType() :
-          null;
-        return type == null ? UNKNOWN_NAME : type.getFullQualifiedName().getFullQualifiedNameAsString();
+  private String getType(final UriResource segment) {
+    final EdmType type = segment instanceof UriResourcePartTyped ? ((UriResourcePartTyped) segment).getType() : null;
+    return type == null ? UNKNOWN_NAME : type.getFullQualifiedName().getFullQualifiedNameAsString();
   }
-
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
index 916307f..e034e8b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
@@ -38,6 +38,7 @@ import org.apache.olingo.server.api.uri.UriInfoResource;
 import org.apache.olingo.server.api.uri.UriInfoService;
 import org.apache.olingo.server.api.uri.UriResource;
 import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
 import org.apache.olingo.server.api.uri.queryoption.CountOption;
 import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption;
 import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -144,11 +145,6 @@ public class UriInfoImpl implements UriInfo {
     return this;
   }
 
-  public UriInfoImpl removeResourcePart(final int index) {
-    pathParts.remove(index);
-    return this;
-  }
-
   public UriResource getLastResourcePart() {
     return lastResourcePart;
   }
@@ -195,6 +191,7 @@ public class UriInfoImpl implements UriInfo {
     case SKIPTOKEN:
     case TOP:
     case LEVELS:
+    case APPLY:
       systemQueryOptions.put(systemQueryOptionKind, systemOption);
       break;
     default:
@@ -259,6 +256,11 @@ public class UriInfoImpl implements UriInfo {
   }
 
   @Override
+  public ApplyOption getApplyOption() {
+    return (ApplyOption) systemQueryOptions.get(SystemQueryOptionKind.APPLY);
+  }
+
+  @Override
   public List<SystemQueryOption> getSystemQueryOptions() {
     return Collections.unmodifiableList(new ArrayList<SystemQueryOption>(systemQueryOptions.values()));
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
new file mode 100644
index 0000000..9872eb1
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
@@ -0,0 +1,570 @@
+/*
+ * 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.server.core.uri.parser;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmElement;
+import org.apache.olingo.commons.api.edm.EdmFunction;
+import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
+import org.apache.olingo.commons.api.edm.EdmParameter;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.EdmProperty;
+import org.apache.olingo.commons.api.edm.EdmReturnType;
+import org.apache.olingo.commons.api.edm.EdmStructuredType;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceKind;
+import org.apache.olingo.server.api.uri.UriResourcePartTyped;
+import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
+import org.apache.olingo.server.api.uri.queryoption.FilterOption;
+import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+import org.apache.olingo.server.core.uri.UriInfoImpl;
+import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
+import org.apache.olingo.server.core.uri.UriResourceCountImpl;
+import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl;
+import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl;
+import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
+import org.apache.olingo.server.core.uri.queryoption.ApplyOptionImpl;
+import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl;
+import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.AggregateExpressionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.AggregateImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.BottomTopImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ComputeExpressionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ComputeImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ConcatImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.CustomFunctionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.DynamicProperty;
+import org.apache.olingo.server.core.uri.queryoption.apply.DynamicStructuredType;
+import org.apache.olingo.server.core.uri.queryoption.apply.ExpandImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.FilterImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.GroupByImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.GroupByItemImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.IdentityImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.SearchImpl;
+import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl;
+import org.apache.olingo.server.core.uri.validator.UriValidationException;
+
+public class ApplyParser {
+
+  private static final Map<TokenKind, StandardMethod> TOKEN_KIND_TO_STANDARD_METHOD;
+  static {
+    Map<TokenKind, StandardMethod> temp = new EnumMap<TokenKind, StandardMethod>(TokenKind.class);
+    temp.put(TokenKind.SUM, StandardMethod.SUM);
+    temp.put(TokenKind.MIN, StandardMethod.MIN);
+    temp.put(TokenKind.MAX, StandardMethod.MAX);
+    temp.put(TokenKind.AVERAGE, StandardMethod.AVERAGE);
+    temp.put(TokenKind.COUNTDISTINCT, StandardMethod.COUNT_DISTINCT);
+    TOKEN_KIND_TO_STANDARD_METHOD = Collections.unmodifiableMap(temp);
+  }
+
+  private static final Map<TokenKind, BottomTop.Method> TOKEN_KIND_TO_BOTTOM_TOP_METHOD;
+  static {
+    Map<TokenKind, BottomTop.Method> temp = new EnumMap<TokenKind, BottomTop.Method>(TokenKind.class);
+    temp.put(TokenKind.BottomCountTrafo, BottomTop.Method.BOTTOM_COUNT);
+    temp.put(TokenKind.BottomPercentTrafo, BottomTop.Method.BOTTOM_PERCENT);
+    temp.put(TokenKind.BottomSumTrafo, BottomTop.Method.BOTTOM_SUM);
+    temp.put(TokenKind.TopCountTrafo, BottomTop.Method.TOP_COUNT);
+    temp.put(TokenKind.TopPercentTrafo, BottomTop.Method.TOP_PERCENT);
+    temp.put(TokenKind.TopSumTrafo, BottomTop.Method.TOP_SUM);
+    TOKEN_KIND_TO_BOTTOM_TOP_METHOD = Collections.unmodifiableMap(temp);
+  }
+
+  private final Edm edm;
+  private final OData odata;
+
+  private UriTokenizer tokenizer;
+  private Collection<String> crossjoinEntitySetNames;
+  private Map<String, AliasQueryOption> aliases;
+
+  public ApplyParser(final Edm edm, final OData odata) {
+    this.edm = edm;
+    this.odata = odata;
+  }
+
+  public ApplyOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType,
+      final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases)
+      throws UriParserException, UriValidationException {
+    this.tokenizer = tokenizer;
+    this.crossjoinEntitySetNames = crossjoinEntitySetNames;
+    this.aliases = aliases;
+
+    // TODO: Check when to create a new dynamic type and how it can be returned.
+    DynamicStructuredType type = new DynamicStructuredType(referencedType);
+    return parseApply(type);
+  }
+
+  private ApplyOption parseApply(EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    ApplyOptionImpl option = new ApplyOptionImpl();
+    do {
+      option.add(parseTrafo(referencedType));
+    } while (tokenizer.next(TokenKind.SLASH));
+    return option;
+  }
+
+  private ApplyItem parseTrafo(EdmStructuredType referencedType) throws UriParserException, UriValidationException {
+    if (tokenizer.next(TokenKind.AggregateTrafo)) {
+      return parseAggregateTrafo(referencedType);
+
+    } else if (tokenizer.next(TokenKind.IDENTITY)) {
+      return new IdentityImpl();
+
+    } else if (tokenizer.next(TokenKind.ComputeTrafo)) {
+      return parseComputeTrafo(referencedType);
+
+    } else if (tokenizer.next(TokenKind.ConcatMethod)) {
+      return parseConcatTrafo(referencedType);
+
+    } else if (tokenizer.next(TokenKind.ExpandTrafo)) {
+      return new ExpandImpl().setExpandOption(parseExpandTrafo(referencedType));
+
+    } else if (tokenizer.next(TokenKind.FilterTrafo)) {
+      final FilterOption filterOption = new FilterParser(edm, odata)
+          .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+      ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+      return new FilterImpl().setFilterOption(filterOption);
+
+    } else if (tokenizer.next(TokenKind.GroupByTrafo)) {
+      return parseGroupByTrafo(referencedType);
+
+    } else if (tokenizer.next(TokenKind.SearchTrafo)) {
+      final SearchOption searchOption = new SearchParser().parse(tokenizer);
+      ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+      return new SearchImpl().setSearchOption(searchOption);
+
+    } else if (tokenizer.next(TokenKind.QualifiedName)) {
+      return parseCustomFunction(new FullQualifiedName(tokenizer.getText()), referencedType);
+
+    } else {
+      final TokenKind kind = ParserHelper.next(tokenizer,
+          TokenKind.BottomCountTrafo, TokenKind.BottomPercentTrafo, TokenKind.BottomSumTrafo,
+          TokenKind.TopCountTrafo, TokenKind.TopPercentTrafo, TokenKind.TopSumTrafo);
+      if (kind == null) {
+        throw new UriParserSyntaxException("Invalid apply expression syntax.",
+            UriParserSyntaxException.MessageKeys.SYNTAX);
+      } else {
+        return parseBottomTop(kind, referencedType);
+      }
+    }
+  }
+
+  private Aggregate parseAggregateTrafo(EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    AggregateImpl aggregate = new AggregateImpl();
+    do {
+      aggregate.addExpression(parseAggregateExpr(referencedType));
+    } while (tokenizer.next(TokenKind.COMMA));
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return aggregate;
+  }
+
+  private AggregateExpression parseAggregateExpr(EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    AggregateExpressionImpl aggregateExpression = new AggregateExpressionImpl();
+    tokenizer.saveState();
+
+    // First try is checking for a (potentially empty) path prefix and the things that could follow it.
+    UriInfoImpl uriInfo = new UriInfoImpl();
+    final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
+    if (identifierLeft != null) {
+      final String customAggregate = tokenizer.getText();
+      // A custom aggregate (an OData identifier) is defined in the CustomAggregate
+      // EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
+      // Currently we don't look into annotations, so all custom aggregates are allowed and have no type.
+      uriInfo.addResourcePart(new UriResourcePrimitivePropertyImpl(createDynamicProperty(customAggregate, null)));
+      aggregateExpression.setPath(uriInfo);
+      final String alias = parseAsAlias(referencedType, false);
+      aggregateExpression.setAlias(alias);
+      if (alias != null) {
+        ((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, null));
+      }
+      parseAggregateFrom(aggregateExpression, referencedType);
+    } else if (tokenizer.next(TokenKind.OPEN)) {
+      final UriResource lastResourcePart = uriInfo.getLastResourcePart();
+      if (lastResourcePart == null) {
+        throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
+            UriParserSyntaxException.MessageKeys.SYNTAX);
+      }
+      aggregateExpression.setPath(uriInfo);
+      DynamicStructuredType inlineType = new DynamicStructuredType((EdmStructuredType)
+          ParserHelper.getTypeInformation((UriResourcePartTyped) lastResourcePart));
+      aggregateExpression.setInlineAggregateExpression(parseAggregateExpr(inlineType));
+      ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    } else if (tokenizer.next(TokenKind.COUNT)) {
+      uriInfo.addResourcePart(new UriResourceCountImpl());
+      aggregateExpression.setPath(uriInfo);
+      final String alias = parseAsAlias(referencedType, true);
+      aggregateExpression.setAlias(alias);
+      ((DynamicStructuredType) referencedType).addProperty(
+          createDynamicProperty(alias,
+              // The OData standard mandates Edm.Decimal (with no decimals), although counts are always integer.
+              odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal)));
+    } else {
+      // No legitimate continuation of a path prefix has been found.
+
+      // Second try is checking for a common expression.
+      tokenizer.returnToSavedState();
+      final Expression expression = new ExpressionParser(edm, odata)
+          .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+      aggregateExpression.setExpression(expression);
+      parseAggregateWith(aggregateExpression);
+      if (aggregateExpression.getStandardMethod() == null && aggregateExpression.getCustomMethod() == null) {
+        throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
+            UriParserSyntaxException.MessageKeys.SYNTAX);
+      }
+      final String alias = parseAsAlias(referencedType, true);
+      aggregateExpression.setAlias(alias);
+      ((DynamicStructuredType) referencedType).addProperty(
+          createDynamicProperty(alias,
+              // Determine the type for standard methods; there is no way to do this for custom methods.
+              getTypeForAggregateMethod(aggregateExpression.getStandardMethod(),
+                  ExpressionParser.getType(expression))));
+      parseAggregateFrom(aggregateExpression, referencedType);
+    }
+
+    return aggregateExpression;
+  }
+
+  private void parseAggregateWith(AggregateExpressionImpl aggregateExpression) throws UriParserException {
+    if (tokenizer.next(TokenKind.WithOperator)) {
+      final TokenKind kind = ParserHelper.next(tokenizer,
+          TokenKind.SUM, TokenKind.MIN, TokenKind.MAX, TokenKind.AVERAGE, TokenKind.COUNTDISTINCT,
+          TokenKind.QualifiedName);
+      if (kind == null) {
+        throw new UriParserSyntaxException("Invalid 'with' syntax.",
+            UriParserSyntaxException.MessageKeys.SYNTAX);
+      } else if (kind == TokenKind.QualifiedName) {
+        // A custom aggregation method is announced in the CustomAggregationMethods
+        // EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
+        // Currently we don't look into annotations, so all custom aggregation methods are allowed and have no type.
+        aggregateExpression.setCustomMethod(new FullQualifiedName(tokenizer.getText()));
+      } else {
+        aggregateExpression.setStandardMethod(TOKEN_KIND_TO_STANDARD_METHOD.get(kind));
+      }
+    }
+  }
+
+  private EdmType getTypeForAggregateMethod(final StandardMethod method, final EdmType type) {
+    if (method == StandardMethod.SUM || method == StandardMethod.AVERAGE || method == StandardMethod.COUNT_DISTINCT) {
+      return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal);
+    } else if (method == StandardMethod.MIN || method == StandardMethod.MAX) {
+      return type;
+    } else {
+      return null;
+    }
+  }
+
+  private String parseAsAlias(final EdmStructuredType referencedType, final boolean isRequired)
+      throws UriParserException {
+    if (tokenizer.next(TokenKind.AsOperator)) {
+      ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
+      final String name = tokenizer.getText();
+      if (referencedType.getProperty(name) != null) {
+        throw new UriParserSemanticException("Alias '" + name + "' is already a property.",
+            UriParserSemanticException.MessageKeys.IS_PROPERTY, name);
+      }
+      return name;
+    } else if (isRequired) {
+      throw new UriParserSyntaxException("Expected asAlias not found.", UriParserSyntaxException.MessageKeys.SYNTAX);
+    }
+    return null;
+  }
+
+  private void parseAggregateFrom(AggregateExpressionImpl aggregateExpression,
+      final EdmStructuredType referencedType) throws UriParserException {
+    while (tokenizer.next(TokenKind.FromOperator)) {
+      AggregateExpressionImpl from = new AggregateExpressionImpl();
+      from.setExpression(new MemberImpl(parseGroupingProperty(referencedType), referencedType));
+      parseAggregateWith(from);
+      aggregateExpression.addFrom(from);
+    }
+  }
+
+  private EdmProperty createDynamicProperty(final String name, final EdmType type) {
+    return name == null ? null : new DynamicProperty(name, type);
+  }
+
+  private Compute parseComputeTrafo(final EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    ComputeImpl compute = new ComputeImpl();
+    // TODO: Check when to create a new dynamic type and how it can be returned.
+    DynamicStructuredType type = new DynamicStructuredType(referencedType);
+    do {
+      final Expression expression = new ExpressionParser(edm, odata)
+          .parse(tokenizer, type, crossjoinEntitySetNames, aliases);
+      final EdmType expressionType = ExpressionParser.getType(expression);
+      if (expressionType.getKind() != EdmTypeKind.PRIMITIVE) {
+        throw new UriParserSemanticException("Compute expressions must return primitive values.",
+            UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES, "compute");
+      }
+      final String alias = parseAsAlias(type, true);
+      type.addProperty(createDynamicProperty(alias, expressionType));
+      compute.addExpression(new ComputeExpressionImpl()
+          .setExpression(expression)
+          .setAlias(alias));
+    } while (tokenizer.next(TokenKind.COMMA));
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return compute;
+  }
+
+  private Concat parseConcatTrafo(final EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    ConcatImpl concat = new ConcatImpl();
+    // TODO: Check when to create a new dynamic type and how it can be returned.
+    concat.addApplyOption(parseApply(referencedType));
+    ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+    do {
+      concat.addApplyOption(parseApply(referencedType));
+    } while (tokenizer.next(TokenKind.COMMA));
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return concat;
+  }
+
+  private ExpandOption parseExpandTrafo(final EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    ExpandItemImpl item = new ExpandItemImpl();
+    item.setResourcePath(ExpandParser.parseExpandPath(tokenizer, edm, referencedType, item));
+    final EdmType type = ParserHelper.getTypeInformation((UriResourcePartTyped)
+        ((UriInfoImpl) item.getResourcePath()).getLastResourcePart());
+    if (tokenizer.next(TokenKind.COMMA)) {
+      if (tokenizer.next(TokenKind.FilterTrafo)) {
+        item.setSystemQueryOption(
+            new FilterParser(edm, odata).parse(tokenizer,type, crossjoinEntitySetNames, aliases));
+        ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+      } else {
+        ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
+        item.setSystemQueryOption(parseExpandTrafo((EdmStructuredType) type));
+      }
+    }
+    while (tokenizer.next(TokenKind.COMMA)) {
+      ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
+      final ExpandOption nestedExpand = parseExpandTrafo((EdmStructuredType) type);
+      if (item.getExpandOption() == null) {
+        item.setSystemQueryOption(nestedExpand);
+      } else {
+        // Add to the existing items.
+        ((ExpandOptionImpl) item.getExpandOption())
+            .addExpandItem(nestedExpand.getExpandItems().get(0));
+      }
+    }
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    ExpandOptionImpl expand = new ExpandOptionImpl();
+    expand.addExpandItem(item);
+    return expand;
+  }
+
+  private GroupBy parseGroupByTrafo(final EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    GroupByImpl groupBy = new GroupByImpl();
+    parseGroupByList(groupBy, referencedType);
+    if (tokenizer.next(TokenKind.COMMA)) {
+      groupBy.setApplyOption(parseApply(referencedType));
+    }
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return groupBy;
+  }
+
+  private void parseGroupByList(GroupByImpl groupBy, final EdmStructuredType referencedType)
+      throws UriParserException {
+    ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
+    do {
+      groupBy.addGroupByItem(parseGroupByElement(referencedType));
+    } while (tokenizer.next(TokenKind.COMMA));
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+  }
+
+  private GroupByItem parseGroupByElement(final EdmStructuredType referencedType)
+      throws UriParserException {
+    if (tokenizer.next(TokenKind.RollUpSpec)) {
+      return parseRollUpSpec(referencedType);
+    } else {
+      return new GroupByItemImpl().setPath(parseGroupingProperty(referencedType));
+    }
+  }
+
+  private GroupByItem parseRollUpSpec(final EdmStructuredType referencedType)
+      throws UriParserException {
+    GroupByItemImpl item = new GroupByItemImpl();
+    if (tokenizer.next(TokenKind.ROLLUP_ALL)) {
+      item.setIsRollupAll();
+    } else {
+      item.addRollupItem(new GroupByItemImpl().setPath(
+          parseGroupingProperty(referencedType)));
+    }
+    ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+    do {
+      item.addRollupItem(new GroupByItemImpl().setPath(
+          parseGroupingProperty(referencedType)));
+    } while (tokenizer.next(TokenKind.COMMA));
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return item;
+  }
+
+  private UriInfo parseGroupingProperty(final EdmStructuredType referencedType) throws UriParserException {
+    UriInfoImpl uriInfo = new UriInfoImpl();
+    final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
+    if (identifierLeft != null) {
+      throw new UriParserSemanticException("Unknown identifier in grouping property path.",
+          UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE,
+          identifierLeft,
+          uriInfo.getLastResourcePart() != null && uriInfo.getLastResourcePart() instanceof UriResourcePartTyped ?
+              ((UriResourcePartTyped) uriInfo.getLastResourcePart())
+                  .getType().getFullQualifiedName().getFullQualifiedNameAsString() :
+              "");
+    }
+    if (uriInfo.getLastResourcePart() != null
+        && uriInfo.getLastResourcePart().getKind() == UriResourceKind.navigationProperty) {
+      if (tokenizer.next(TokenKind.SLASH)) {
+        UriResourceNavigationPropertyImpl lastPart = (UriResourceNavigationPropertyImpl) uriInfo.getLastResourcePart();
+        final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm,
+            (EdmStructuredType) lastPart.getType());
+        lastPart.setCollectionTypeFilter(typeCast);
+      }
+    }
+    return uriInfo;
+  }
+
+  /**
+   * Parses the path prefix and a following OData identifier as one path, deviating from the ABNF.
+   * @param uriInfo object to be filled with path segments
+   * @return a parsed but not used OData identifier */
+  private String parsePathPrefix(UriInfoImpl uriInfo, final EdmStructuredType referencedType)
+      throws UriParserException {
+    final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm, referencedType);
+    if (typeCast != null) {
+      ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
+    }
+    EdmStructuredType type = typeCast == null ? referencedType : typeCast;
+    while (tokenizer.next(TokenKind.ODataIdentifier)) {
+      final String name = tokenizer.getText();
+      final EdmElement property = type.getProperty(name);
+      final UriResource segment = parsePathSegment(property);
+      if (segment == null) {
+        if (property == null) {
+          return name;
+        } else {
+          uriInfo.addResourcePart(
+              property instanceof EdmNavigationProperty ?
+                  new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property) :
+                  property.getType().getKind() == EdmTypeKind.COMPLEX ?
+                      new UriResourceComplexPropertyImpl((EdmProperty) property) :
+                      new UriResourcePrimitivePropertyImpl((EdmProperty) property));
+          return null;
+        }
+      } else {
+        uriInfo.addResourcePart(segment);
+      }
+      type = (EdmStructuredType) ParserHelper.getTypeInformation((UriResourcePartTyped) segment);
+    }
+    return null;
+  }
+
+  private UriResource parsePathSegment(final EdmElement property) throws UriParserException {
+    if (property == null
+        || !(property.getType().getKind() == EdmTypeKind.COMPLEX
+        || property instanceof EdmNavigationProperty)) {
+      // Could be a customAggregate or $count.
+      return null;
+    }
+    if (tokenizer.next(TokenKind.SLASH)) {
+      final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm,
+          (EdmStructuredType) property.getType());
+      if (typeCast != null) {
+        ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
+      }
+      return property.getType().getKind() == EdmTypeKind.COMPLEX ?
+          new UriResourceComplexPropertyImpl((EdmProperty) property).setTypeFilter(typeCast) :
+          new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property).setCollectionTypeFilter(typeCast);
+    } else {
+      return null;
+    }
+  }
+
+  private CustomFunction parseCustomFunction(final FullQualifiedName functionName,
+      final EdmStructuredType referencedType) throws UriParserException, UriValidationException {
+    final List<UriParameter> parameters =
+        ParserHelper.parseFunctionParameters(tokenizer, edm, referencedType, true, aliases);
+    final List<String> parameterNames = ParserHelper.getParameterNames(parameters);
+    final EdmFunction function = edm.getBoundFunction(functionName,
+        referencedType.getFullQualifiedName(), true, parameterNames);
+    if (function == null) {
+      throw new UriParserSemanticException("No function '" + functionName + "' found.",
+          UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND,
+          functionName.getFullQualifiedNameAsString());
+    }
+    ParserHelper.validateFunctionParameters(function, parameters, edm, referencedType, aliases);
+
+    // The binding parameter and the return type must be of type complex or entity collection.
+    final EdmParameter bindingParameter = function.getParameter(function.getParameterNames().get(0));
+    final EdmReturnType returnType = function.getReturnType();
+    if (bindingParameter.getType().getKind() != EdmTypeKind.ENTITY
+        && bindingParameter.getType().getKind() != EdmTypeKind.COMPLEX
+        || !bindingParameter.isCollection()
+        || returnType.getType().getKind() != EdmTypeKind.ENTITY
+        && returnType.getType().getKind() != EdmTypeKind.COMPLEX
+        || !returnType.isCollection()) {
+      throw new UriParserSemanticException("Only entity- or complex-collection functions are allowed.",
+          UriParserSemanticException.MessageKeys.FUNCTION_MUST_USE_COLLECTIONS,
+          functionName.getFullQualifiedNameAsString());
+    }
+
+    return new CustomFunctionImpl().setFunction(function).setParameters(parameters);
+  }
+
+  private BottomTop parseBottomTop(final TokenKind kind, final EdmStructuredType referencedType)
+      throws UriParserException, UriValidationException {
+    BottomTopImpl bottomTop = new BottomTopImpl();
+    bottomTop.setMethod(TOKEN_KIND_TO_BOTTOM_TOP_METHOD.get(kind));
+    final ExpressionParser expressionParser = new ExpressionParser(edm, odata);
+    final Expression number = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+    expressionParser.checkIntegerType(number);
+    bottomTop.setNumber(number);
+    ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+    final Expression value = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+    expressionParser.checkNumericType(value);
+    bottomTop.setValue(value);
+    ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+    return bottomTop;
+  }
+}


[2/3] olingo-odata4 git commit: [OLINGO-935]URI-parser support of the data aggregation extension

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java
index 0cc1977..eb5d679 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java
@@ -38,6 +38,8 @@ import org.apache.olingo.server.api.uri.UriResourcePartTyped;
 import org.apache.olingo.server.api.uri.UriResourceRef;
 import org.apache.olingo.server.api.uri.UriResourceValue;
 import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
 import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
 import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
 import org.apache.olingo.server.api.uri.queryoption.FilterOption;
@@ -53,6 +55,7 @@ import org.apache.olingo.server.core.uri.UriResourceStartingTypeFilterImpl;
 import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
 import org.apache.olingo.server.core.uri.parser.search.SearchParser;
 import org.apache.olingo.server.core.uri.queryoption.AliasQueryOptionImpl;
+import org.apache.olingo.server.core.uri.queryoption.ApplyOptionImpl;
 import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl;
 import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
 import org.apache.olingo.server.core.uri.queryoption.FilterOptionImpl;
@@ -206,6 +209,8 @@ public class Parser {
     }
 
     // Post-process system query options that need context information from the resource path.
+    parseApplyOption(contextUriInfo.getApplyOption(), contextType,
+        contextUriInfo.getEntitySetNames(), contextUriInfo.getAliasMap());
     parseFilterOption(contextUriInfo.getFilterOption(), contextType,
         contextUriInfo.getEntitySetNames(), contextUriInfo.getAliasMap());
     parseOrderByOption(contextUriInfo.getOrderByOption(), contextType,
@@ -226,7 +231,7 @@ public class Parser {
         throw new UriParserSyntaxException("Unknown system query option!",
             UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, optionName);
       }
-      final SystemQueryOptionImpl systemOption;
+      SystemQueryOptionImpl systemOption;
       switch (kind) {
       case SEARCH:
         SearchOption searchOption = new SearchParser().parse(optionValue);
@@ -293,6 +298,9 @@ public class Parser {
       case LEVELS:
         throw new UriParserSyntaxException("System query option '$levels' is allowed only inside '$expand'!",
             UriParserSyntaxException.MessageKeys.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE);
+      case APPLY:
+        systemOption = new ApplyOptionImpl();
+        break;
       default:
           throw new UriParserSyntaxException("System query option '" + kind + "' is not known!",
               UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, optionName);
@@ -344,8 +352,8 @@ public class Parser {
   }
 
   private void parseExpandOption(ExpandOption expandOption, final EdmType contextType, final boolean isAll,
-      final List<String> entitySetNames, final Map<String, AliasQueryOption> aliases) throws UriParserException,
-      UriValidationException {
+      final List<String> entitySetNames, final Map<String, AliasQueryOption> aliases)
+      throws UriParserException, UriValidationException {
     if (expandOption != null) {
       if (!(contextType instanceof EdmStructuredType || isAll
       || (entitySetNames != null && !entitySetNames.isEmpty()))) {
@@ -377,6 +385,23 @@ public class Parser {
     }
   }
 
+  private void parseApplyOption(ApplyOption applyOption, final EdmType contextType,
+      final List<String> entitySetNames, final Map<String, AliasQueryOption> aliases)
+      throws UriParserException, UriValidationException {
+    if (applyOption != null) {
+      final String optionValue = applyOption.getText();
+      UriTokenizer applyTokenizer = new UriTokenizer(optionValue);
+      final ApplyOption option = new ApplyParser(edm, odata).parse(applyTokenizer,
+          contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null,
+          entitySetNames,
+          aliases);
+      checkOptionEOF(applyTokenizer, applyOption.getName(), optionValue);
+      for (final ApplyItem item : option.getApplyItems()) {
+        ((ApplyOptionImpl) applyOption).add(item);
+      }
+    }
+  }
+
   private void ensureLastSegment(final String segment, final int pos, final int size)
       throws UriParserSyntaxException {
     if (pos < size) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java
index db33a6a..f2c4634 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java
@@ -67,9 +67,15 @@ public class UriParserSemanticException extends UriParserException {
     COMPLEX_PARAMETER_IN_RESOURCE_PATH,
     /** parameters: left type, right type */
     TYPES_NOT_COMPATIBLE,
-    /** parameter: addressed resource name*/
-    NOT_A_MEDIA_RESOURCE;
-    
+    /** parameter: addressed resource name */
+    NOT_A_MEDIA_RESOURCE,
+    /** parameters: property name */
+    IS_PROPERTY,
+    /** parameter: expression */
+    ONLY_FOR_PRIMITIVE_TYPES,
+    /** parameter: function name */
+    FUNCTION_MUST_USE_COLLECTIONS;
+
     @Override
     public String getKey() {
       return name();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java
index d218666..6654df9 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java
@@ -23,7 +23,8 @@ package org.apache.olingo.server.core.uri.parser;
  * <p>As far as feasible, it tries to work on character basis, assuming this to be faster than string operations.
  * Since only the index is "moved", backing out while parsing a token is easy and used throughout.
  * There is intentionally no method to push back tokens (although it would be easy to add such a method)
- * because this tokenizer should behave like a classical token-consuming tokenizer.</p>
+ * because this tokenizer should behave like a classical token-consuming tokenizer.
+ * There is, however, the possibility to save the current state and return to it later.</p>
  * <p>Whitespace is not an extra token but consumed with the tokens that require whitespace.
  * Optional whitespace is not supported.</p>
  */
@@ -40,6 +41,7 @@ public class UriTokenizer {
     ROOT,
     IT,
 
+    APPLY, // for the aggregation extension
     EXPAND,
     FILTER,
     LEVELS,
@@ -66,6 +68,13 @@ public class UriTokenizer {
     NULL,
     MAX,
 
+    AVERAGE, // for the aggregation extension
+    COUNTDISTINCT, // for the aggregation extension
+    IDENTITY, // for the aggregation extension
+    MIN, // for the aggregation extension
+    SUM, // for the aggregation extension
+    ROLLUP_ALL, // for the aggregation extension
+
     // variable-value tokens (convention: mixed case)
     ODataIdentifier,
     QualifiedName,
@@ -125,6 +134,10 @@ public class UriTokenizer {
     MinusOperator,
     NotOperator,
 
+    AsOperator, // for the aggregation extension
+    FromOperator, // for the aggregation extension
+    WithOperator, // for the aggregation extension
+
     CastMethod,
     CeilingMethod,
     ConcatMethod,
@@ -158,6 +171,23 @@ public class UriTokenizer {
     TrimMethod,
     YearMethod,
 
+    IsDefinedMethod, // for the aggregation extension
+
+    AggregateTrafo, // for the aggregation extension
+    BottomCountTrafo, // for the aggregation extension
+    BottomPercentTrafo, // for the aggregation extension
+    BottomSumTrafo, // for the aggregation extension
+    ComputeTrafo, // for the aggregation extension
+    ExpandTrafo, // for the aggregation extension
+    FilterTrafo, // for the aggregation extension
+    GroupByTrafo, // for the aggregation extension
+    SearchTrafo, // for the aggregation extension
+    TopCountTrafo, // for the aggregation extension
+    TopPercentTrafo, // for the aggregation extension
+    TopSumTrafo, // for the aggregation extension
+
+    RollUpSpec, // for the aggregation extension
+
     AscSuffix,
     DescSuffix
   }
@@ -167,10 +197,31 @@ public class UriTokenizer {
   private int startIndex = 0;
   private int index = 0;
 
+  private int savedStartIndex;
+  private int savedIndex;
+
   public UriTokenizer(final String parseString) {
     this.parseString = parseString == null ? "" : parseString;
   }
 
+  /**
+   * Save the current state.
+   * @see #returnToSavedState()
+   */
+  public void saveState() {
+    savedStartIndex = startIndex;
+    savedIndex = index;
+  }
+
+  /**
+   * Return to the previously saved state.
+   * @see #saveState()
+   */
+  public void returnToSavedState() {
+    startIndex = savedStartIndex;
+    index = savedIndex;
+  }
+
   /** Returns the string value corresponding to the last successful {@link #next(TokenKind)} call. */
   public String getText() {
     return parseString.substring(startIndex, index);
@@ -218,6 +269,9 @@ public class UriTokenizer {
       found = nextConstant("$it");
       break;
 
+    case APPLY:
+      found = nextConstant("$apply");
+      break;
     case EXPAND:
       found = nextConstant("$expand");
       break;
@@ -288,6 +342,26 @@ public class UriTokenizer {
       found = nextConstant("max");
       break;
 
+    case AVERAGE:
+      found = nextConstant("average");
+      break;
+    case COUNTDISTINCT:
+      found = nextConstant("countdistinct");
+      break;
+    case IDENTITY:
+      found = nextConstant("identity");
+      break;
+    case MIN:
+      found = nextConstant("min");
+      break;
+    case SUM:
+      found = nextConstant("sum");
+      break;
+
+    case ROLLUP_ALL:
+      found = nextConstant("$all");
+      break;
+
     // Identifiers
     case ODataIdentifier:
       found = nextODataIdentifier();
@@ -456,6 +530,17 @@ public class UriTokenizer {
       found = nextUnaryOperator("not");
       break;
 
+    // Operators for the aggregation extension
+    case AsOperator:
+      found = nextBinaryOperator("as");
+      break;
+    case FromOperator:
+      found = nextBinaryOperator("from");
+      break;
+    case WithOperator:
+      found = nextBinaryOperator("with");
+      break;
+
     // Methods
     case CastMethod:
       found = nextMethod("cast");
@@ -554,6 +639,54 @@ public class UriTokenizer {
       found = nextMethod("year");
       break;
 
+    // Method for the aggregation extension
+    case IsDefinedMethod:
+      found = nextMethod("isdefined");
+      break;
+
+    // Transformations for the aggregation extension
+    case AggregateTrafo:
+      found = nextMethod("aggregate");
+      break;
+    case BottomCountTrafo:
+      found = nextMethod("bottomcount");
+      break;
+    case BottomPercentTrafo:
+      found = nextMethod("bottompercent");
+      break;
+    case BottomSumTrafo:
+      found = nextMethod("bottomsum");
+      break;
+    case ComputeTrafo:
+      found = nextMethod("compute");
+      break;
+    case ExpandTrafo:
+      found = nextMethod("expand");
+      break;
+    case FilterTrafo:
+      found = nextMethod("filter");
+      break;
+    case GroupByTrafo:
+      found = nextMethod("groupby");
+      break;
+    case SearchTrafo:
+      found = nextMethod("search");
+      break;
+    case TopCountTrafo:
+      found = nextMethod("topcount");
+      break;
+    case TopPercentTrafo:
+      found = nextMethod("toppercent");
+      break;
+    case TopSumTrafo:
+      found = nextMethod("topsum");
+      break;
+
+    // Roll-up specification for the aggregation extension
+    case RollUpSpec:
+      found = nextMethod("rollup");
+      break;
+
     // Suffixes
     case AscSuffix:
       found = nextSuffix("asc");

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/ApplyOptionImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/ApplyOptionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/ApplyOptionImpl.java
new file mode 100644
index 0000000..066352d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/ApplyOptionImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.core.uri.queryoption;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;
+
+public class ApplyOptionImpl extends SystemQueryOptionImpl implements ApplyOption {
+
+  private List<ApplyItem> transformations = new ArrayList<ApplyItem>();
+
+  public ApplyOptionImpl() {
+    setKind(SystemQueryOptionKind.APPLY);
+  }
+
+  @Override
+  public List<ApplyItem> getApplyItems() {
+    return Collections.unmodifiableList(transformations);
+  }
+
+  public ApplyOptionImpl add(final ApplyItem transformation) {
+    transformations.add(transformation);
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java
new file mode 100644
index 0000000..1bacfe0
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateExpressionImpl.java
@@ -0,0 +1,113 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents an aggregate expression.
+ */
+public class AggregateExpressionImpl implements AggregateExpression {
+
+  private UriInfo path;
+  private Expression expression;
+  private StandardMethod standardMethod;
+  private FullQualifiedName customMethod;
+  private String alias;
+  private AggregateExpression inlineAggregateExpression;
+  private List<AggregateExpression> from = new ArrayList<AggregateExpression>();
+
+  @Override
+  public List<UriResource> getPath() {
+    return path == null ? Collections.<UriResource> emptyList() : path.getUriResourceParts();
+  }
+
+  public AggregateExpressionImpl setPath(final UriInfo uriInfo) {
+    path = uriInfo;
+    return this;
+  }
+
+  @Override
+  public Expression getExpression() {
+    return expression;
+  }
+
+  public AggregateExpressionImpl setExpression(final Expression expression) {
+    this.expression = expression;
+    return this;
+  }
+
+  @Override
+  public StandardMethod getStandardMethod() {
+    return standardMethod;
+  }
+
+  public AggregateExpressionImpl setStandardMethod(final StandardMethod standardMethod) {
+    this.standardMethod = standardMethod;
+    return this;
+  }
+
+  @Override
+  public FullQualifiedName getCustomMethod() {
+    return customMethod;
+  }
+
+  public AggregateExpressionImpl setCustomMethod(final FullQualifiedName customMethod) {
+    this.customMethod = customMethod;
+    return this;
+  }
+
+  @Override
+  public AggregateExpression getInlineAggregateExpression() {
+    return inlineAggregateExpression;
+  }
+
+  public AggregateExpressionImpl setInlineAggregateExpression(final AggregateExpression aggregateExpression) {
+    inlineAggregateExpression = aggregateExpression;
+    return this;
+  }
+
+  @Override
+  public List<AggregateExpression> getFrom() {
+    return Collections.unmodifiableList(from);
+  }
+
+  public AggregateExpressionImpl addFrom(final AggregateExpression from) {
+    this.from.add(from);
+    return this;
+  }
+
+  @Override
+  public String getAlias() {
+    return alias;
+  }
+
+  public AggregateExpressionImpl setAlias(final String alias) {
+    this.alias = alias;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateImpl.java
new file mode 100644
index 0000000..3414d2d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/AggregateImpl.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+
+/**
+ * Represents the aggregate transformation.
+ */
+public class AggregateImpl implements Aggregate {
+
+  private List<AggregateExpression> expressions = new ArrayList<AggregateExpression>();
+
+  @Override
+  public Kind getKind() {
+    return Kind.AGGREGATE;
+  }
+
+  @Override
+  public List<AggregateExpression> getExpressions() {
+    return expressions;
+  }
+
+  public AggregateImpl addExpression(final AggregateExpression expression) {
+    expressions.add(expression);
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/BottomTopImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/BottomTopImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/BottomTopImpl.java
new file mode 100644
index 0000000..3deb895
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/BottomTopImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents a transformation with one of the pre-defined methods
+ * <code>bottomcount</code>, <code>bottompercent</code>, <code>bottomsum</code>,
+ * <code>topcount</code>, <code>toppercent</code>, <code>topsum</code>.
+ */
+public class BottomTopImpl implements BottomTop {
+
+  private Method method;
+  private Expression number;
+  private Expression value;
+
+  @Override
+  public Kind getKind() {
+    return Kind.BOTTOM_TOP;
+  }
+
+  @Override
+  public Method getMethod() {
+    return method;
+  }
+
+  public BottomTopImpl setMethod(final Method method) {
+    this.method = method;
+    return this;
+  }
+
+  @Override
+  public Expression getNumber() {
+    return number;
+  }
+
+  public BottomTopImpl setNumber(final Expression number) {
+    this.number = number;
+    return this;
+  }
+
+  @Override
+  public Expression getValue() {
+    return value;
+  }
+
+  public BottomTopImpl setValue(final Expression value) {
+    this.value = value;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeExpressionImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeExpressionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeExpressionImpl.java
new file mode 100644
index 0000000..c073aa3
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeExpressionImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents an aggregate expression.
+ */
+public class ComputeExpressionImpl implements ComputeExpression {
+
+  private Expression expression;
+  private String alias;
+
+  @Override
+  public Expression getExpression() {
+    return expression;
+  }
+
+  public ComputeExpressionImpl setExpression(final Expression expression) {
+    this.expression = expression;
+    return this;
+  }
+
+  @Override
+  public String getAlias() {
+    return alias;
+  }
+
+  public ComputeExpressionImpl setAlias(final String alias) {
+    this.alias = alias;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeImpl.java
new file mode 100644
index 0000000..8e57120
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ComputeImpl.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
+
+/**
+ * Represents the compute transformation.
+ */
+public class ComputeImpl implements Compute {
+
+  private List<ComputeExpression> expressions = new ArrayList<ComputeExpression>();
+
+  @Override
+  public Kind getKind() {
+    return Kind.COMPUTE;
+  }
+
+  @Override
+  public List<ComputeExpression> getExpressions() {
+    return expressions;
+  }
+
+  public ComputeImpl addExpression(final ComputeExpressionImpl expression) {
+    expressions.add(expression);
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ConcatImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ConcatImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ConcatImpl.java
new file mode 100644
index 0000000..0a3b919
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ConcatImpl.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+
+/**
+ * Represents the concat transformation.
+ */
+public class ConcatImpl implements Concat {
+
+  private List<ApplyOption> options = new ArrayList<ApplyOption>();
+
+  @Override
+  public Kind getKind() {
+    return Kind.CONCAT;
+  }
+
+  @Override
+  public List<ApplyOption> getApplyOptions() {
+    return options;
+  }
+
+  public ConcatImpl addApplyOption(final ApplyOption option) {
+    options.add(option);
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/CustomFunctionImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/CustomFunctionImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/CustomFunctionImpl.java
new file mode 100644
index 0000000..ba083dd
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/CustomFunctionImpl.java
@@ -0,0 +1,62 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.EdmFunction;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+
+/**
+ * Represents a transformation with a custom function.
+ */
+public class CustomFunctionImpl implements CustomFunction {
+
+  private EdmFunction function = null;
+  private List<UriParameter> parameters;
+
+  @Override
+  public Kind getKind() {
+    return Kind.CUSTOM_FUNCTION;
+  }
+
+  @Override
+  public EdmFunction getFunction() {
+    return function;
+  }
+
+  public CustomFunctionImpl setFunction(final EdmFunction function) {
+    this.function = function;
+    return this;
+  }
+
+  @Override
+  public List<UriParameter> getParameters() {
+    return parameters == null ?
+        Collections.<UriParameter> emptyList() :
+        Collections.unmodifiableList(parameters);
+  }
+
+  public CustomFunctionImpl setParameters(final List<UriParameter> parameters) {
+    this.parameters = parameters;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicProperty.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicProperty.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicProperty.java
new file mode 100644
index 0000000..d9fbde9
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicProperty.java
@@ -0,0 +1,118 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.EdmAnnotation;
+import org.apache.olingo.commons.api.edm.EdmMapping;
+import org.apache.olingo.commons.api.edm.EdmProperty;
+import org.apache.olingo.commons.api.edm.EdmTerm;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
+import org.apache.olingo.commons.api.edm.geo.SRID;
+
+/** A dynamic EDM property containing an aggregation. */
+public class DynamicProperty implements EdmProperty {
+
+  private final String name;
+  private final EdmType propertyType;
+
+  /** Creates a dynamic property with a mandatory name and an optional type. */
+  public DynamicProperty(final String name, final EdmType type) {
+    this.name = name;
+    propertyType = type;
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public EdmType getType() {
+    return propertyType;
+  }
+
+  @Override
+  public boolean isCollection() {
+    return false;
+  }
+
+  @Override
+  public EdmMapping getMapping() {
+    return null;
+  }
+
+  @Override
+  public String getMimeType() {
+    return null;
+  }
+
+  @Override
+  public boolean isNullable() {
+    return false;
+  }
+
+  @Override
+  public Integer getMaxLength() {
+    return null;
+  }
+
+  @Override
+  public Integer getPrecision() {
+    return null;
+  }
+
+  @Override
+  public Integer getScale() {
+    return null;
+  }
+
+  @Override
+  public SRID getSrid() {
+    return null;
+  }
+
+  @Override
+  public boolean isUnicode() {
+    return true;
+  }
+
+  @Override
+  public String getDefaultValue() {
+    return null;
+  }
+
+  @Override
+  public boolean isPrimitive() {
+    return propertyType != null && propertyType.getKind() == EdmTypeKind.PRIMITIVE;
+  }
+
+  @Override
+  public EdmAnnotation getAnnotation(final EdmTerm term, final String qualifier) {
+    return null;
+  }
+
+  @Override
+  public List<EdmAnnotation> getAnnotations() {
+    return Collections.emptyList();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicStructuredType.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicStructuredType.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicStructuredType.java
new file mode 100644
index 0000000..f0ac5c6
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/DynamicStructuredType.java
@@ -0,0 +1,141 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.edm.EdmAnnotation;
+import org.apache.olingo.commons.api.edm.EdmElement;
+import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
+import org.apache.olingo.commons.api.edm.EdmProperty;
+import org.apache.olingo.commons.api.edm.EdmStructuredType;
+import org.apache.olingo.commons.api.edm.EdmTerm;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
+
+/** A dynamic structured type used to incorporate dynamic properties containing aggregations. */
+public class DynamicStructuredType implements EdmStructuredType, Cloneable {
+
+  private final EdmStructuredType startType;
+  private Map<String, EdmProperty> properties;
+
+  public DynamicStructuredType(final EdmStructuredType startType) {
+    this.startType = startType;
+  }
+
+  public DynamicStructuredType addProperty(final EdmProperty property) {
+    if (properties == null) {
+      properties = new LinkedHashMap<String, EdmProperty>();
+    }
+    properties.put(property.getName(), property);
+    return this;
+  }
+
+  @Override
+  public EdmElement getProperty(final String name) {
+    final EdmElement property = startType.getProperty(name);
+    return property == null ?
+        properties == null ? null : properties.get(name) :
+        property;
+  }
+
+  @Override
+  public List<String> getPropertyNames() {
+    if (properties == null || properties.isEmpty()) {
+      return startType.getPropertyNames();
+    } else {
+      List<String> names = new ArrayList<String>(startType.getPropertyNames());
+      names.addAll(properties.keySet());
+      return Collections.unmodifiableList(names);
+    }
+  }
+
+  @Override
+  public EdmProperty getStructuralProperty(final String name) {
+    final EdmProperty property = startType.getStructuralProperty(name);
+    return property == null ?
+        properties == null ? null : properties.get(name) :
+        property;
+  }
+
+  @Override
+  public EdmNavigationProperty getNavigationProperty(final String name) {
+    return startType.getNavigationProperty(name);
+  }
+
+  @Override
+  public List<String> getNavigationPropertyNames() {
+    return startType.getNavigationPropertyNames();
+  }
+
+  @Override
+  public String getNamespace() {
+    return startType.getNamespace();
+  }
+
+  @Override
+  public String getName() {
+    return startType.getName();
+  }
+
+  @Override
+  public FullQualifiedName getFullQualifiedName() {
+    return startType.getFullQualifiedName();
+  }
+
+  @Override
+  public EdmTypeKind getKind() {
+    return startType.getKind();
+  }
+
+  @Override
+  public EdmAnnotation getAnnotation(final EdmTerm term, final String qualifier) {
+    return startType.getAnnotation(term, qualifier);
+  }
+
+  @Override
+  public List<EdmAnnotation> getAnnotations() {
+    return startType.getAnnotations();
+  }
+
+  @Override
+  public EdmStructuredType getBaseType() {
+    return startType.getBaseType();
+  }
+
+  @Override
+  public boolean compatibleTo(final EdmType targetType) {
+    return startType.compatibleTo(targetType);
+  }
+
+  @Override
+  public boolean isOpenType() {
+    return startType.isOpenType();
+  }
+
+  @Override
+  public boolean isAbstract() {
+    return startType.isAbstract();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ExpandImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ExpandImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ExpandImpl.java
new file mode 100644
index 0000000..a9992c5
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/ExpandImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
+
+/**
+ * Represents the expand transformation.
+ */
+public class ExpandImpl implements Expand {
+
+  private ExpandOption expandOption = null;
+
+  @Override
+  public Kind getKind() {
+    return Kind.EXPAND;
+  }
+
+  @Override
+  public ExpandOption getExpandOption() {
+    return expandOption;
+  }
+
+  public ExpandImpl setExpandOption(final ExpandOption expandOption) {
+    this.expandOption = expandOption;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/FilterImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/FilterImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/FilterImpl.java
new file mode 100644
index 0000000..c356845
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/FilterImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.FilterOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
+
+/**
+ * Represents the filter transformation.
+ */
+public class FilterImpl implements Filter {
+
+  private FilterOption filterOption = null;
+
+  @Override
+  public Kind getKind() {
+    return Kind.FILTER;
+  }
+
+  @Override
+  public FilterOption getFilterOption() {
+    return filterOption;
+  }
+
+  public FilterImpl setFilterOption(final FilterOption filterOption) {
+    this.filterOption = filterOption;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByImpl.java
new file mode 100644
index 0000000..9946e17
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+
+/**
+ * Represents the grouping transformation.
+ */
+public class GroupByImpl implements GroupBy {
+
+  private ApplyOption applyOption;
+  private List<GroupByItem> groupByItems = new ArrayList<GroupByItem>();
+
+  @Override
+  public Kind getKind() {
+    return Kind.GROUP_BY;
+  }
+
+  @Override
+  public ApplyOption getApplyOption() {
+    return applyOption;
+  }
+
+  public GroupByImpl setApplyOption(final ApplyOption applyOption) {
+    this.applyOption = applyOption;
+    return this;
+  }
+
+  @Override
+  public List<GroupByItem> getGroupByItems() {
+    return groupByItems;
+  }
+
+  public GroupByImpl addGroupByItem(final GroupByItem groupByItem) {
+    groupByItems.add(groupByItem);
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByItemImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByItemImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByItemImpl.java
new file mode 100644
index 0000000..0dd52b1
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/GroupByItemImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+
+/**
+ * Represents a grouping property.
+ */
+public class GroupByItemImpl implements GroupByItem {
+
+  private UriInfo path;
+  private boolean isRollupAll;
+  private List<GroupByItem> rollup = new ArrayList<GroupByItem>();
+
+  @Override
+  public List<UriResource> getPath() {
+    return path == null ? Collections.<UriResource> emptyList() : path.getUriResourceParts();
+  }
+
+  public GroupByItemImpl setPath(final UriInfo uriInfo) {
+    path = uriInfo;
+    return this;
+  }
+
+  @Override
+  public List<GroupByItem> getRollup() {
+    return rollup;
+  }
+
+  public GroupByItemImpl addRollupItem(final GroupByItem groupByItem) {
+    rollup.add(groupByItem);
+    return this;
+  }
+
+  @Override
+  public boolean isRollupAll() {
+    return isRollupAll;
+  }
+
+  public GroupByItemImpl setIsRollupAll() {
+    this.isRollupAll = true;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/IdentityImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/IdentityImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/IdentityImpl.java
new file mode 100644
index 0000000..838d0b2
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/IdentityImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.apply.Identity;
+
+/**
+ * Represents the identity transformation.
+ */
+public class IdentityImpl implements Identity {
+
+  @Override
+  public Kind getKind() {
+    return Kind.IDENTITY;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/SearchImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/SearchImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/SearchImpl.java
new file mode 100644
index 0000000..9a588d9
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/apply/SearchImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.core.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Search;
+
+/**
+ * Represents the search transformation.
+ */
+public class SearchImpl implements Search {
+
+  private SearchOption searchOption = null;
+
+  @Override
+  public Kind getKind() {
+    return Kind.SEARCH;
+  }
+
+  @Override
+  public SearchOption getSearchOption() {
+    return searchOption;
+  }
+
+  public SearchImpl setSearchOption(final SearchOption searchOption) {
+    this.searchOption = searchOption;
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java
index 0946b60..64c3cf7 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java
@@ -45,27 +45,27 @@ public class UriValidator {
   //CHECKSTYLE:OFF (Maven checkstyle)
   private static final boolean[][] decisionMatrix =
     {
-      /*                                          0-FILTER 1-FORMAT 2-EXPAND 3-ID     4-COUNT  5-ORDERBY 6-SEARCH 7-SELECT 8-SKIP   9-SKIPTOKEN 10-TOP */
-      /*                              all  0 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true  },
-      /*                            batch  1 */ { false,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                        crossjoin  2 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true  },
-      /*                         entityId  3 */ { false,   true ,   true ,   true ,   false,   false,    false,   true ,   false,   false,      false },
-      /*                         metadata  4 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                          service  5 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                        entitySet  6 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true  },
-      /*                   entitySetCount  7 */ { true ,   false,   false,   false,   false,   false,    true ,   false,   false,   false,      false },
-      /*                           entity  8 */ { false,   true ,   true ,   false,   false,   false,    false,   true ,   false,   false,      false },
-      /*                      mediaStream  9 */ { false,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                       references 10 */ { true ,   true ,   false,   false,   true ,   true ,    true ,   false,   true ,   true ,      true  },
-      /*                        reference 11 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                  propertyComplex 12 */ { false,   true ,   true ,   false,   false,   false,    false,   true ,   false,   false,      false },
-      /*        propertyComplexCollection 13 */ { true ,   true ,   true ,   false,   true ,   true ,    false,   true ,   true ,   true ,      true  },
-      /*   propertyComplexCollectionCount 14 */ { true ,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                propertyPrimitive 15 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*      propertyPrimitiveCollection 16 */ { true ,   true ,   false,   false,   true ,   true ,    false,   false,   true ,   true ,      true  },
-      /* propertyPrimitiveCollectionCount 17 */ { true ,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*           propertyPrimitiveValue 18 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false },
-      /*                             none 19 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false }
+      /*                                          0-FILTER 1-FORMAT 2-EXPAND 3-ID     4-COUNT  5-ORDERBY 6-SEARCH 7-SELECT 8-SKIP   9-SKIPTOKEN 10-TOP 11-APPLY */
+      /*                              all  0 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true , true  },
+      /*                            batch  1 */ { false,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                        crossjoin  2 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true , true  },
+      /*                         entityId  3 */ { false,   true ,   true ,   true ,   false,   false,    false,   true ,   false,   false,      false, false },
+      /*                         metadata  4 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                          service  5 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                        entitySet  6 */ { true ,   true ,   true ,   false,   true ,   true ,    true ,   true ,   true ,   true ,      true , true  },
+      /*                   entitySetCount  7 */ { true ,   false,   false,   false,   false,   false,    true ,   false,   false,   false,      false, false },
+      /*                           entity  8 */ { false,   true ,   true ,   false,   false,   false,    false,   true ,   false,   false,      false, false },
+      /*                      mediaStream  9 */ { false,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                       references 10 */ { true ,   true ,   false,   false,   true ,   true ,    true ,   false,   true ,   true ,      true , false },
+      /*                        reference 11 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                  propertyComplex 12 */ { false,   true ,   true ,   false,   false,   false,    false,   true ,   false,   false,      false, false },
+      /*        propertyComplexCollection 13 */ { true ,   true ,   true ,   false,   true ,   true ,    false,   true ,   true ,   true ,      true , true  },
+      /*   propertyComplexCollectionCount 14 */ { true ,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                propertyPrimitive 15 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*      propertyPrimitiveCollection 16 */ { true ,   true ,   false,   false,   true ,   true ,    false,   false,   true ,   true ,      true , false },
+      /* propertyPrimitiveCollectionCount 17 */ { true ,   false,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*           propertyPrimitiveValue 18 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false },
+      /*                             none 19 */ { false,   true ,   false,   false,   false,   false,    false,   false,   false,   false,      false, false }
     };
   //CHECKSTYLE:ON
   //@formatter:on
@@ -118,6 +118,7 @@ public class UriValidator {
     temp.put(SystemQueryOptionKind.SKIP, 8);
     temp.put(SystemQueryOptionKind.SKIPTOKEN, 9);
     temp.put(SystemQueryOptionKind.TOP, 10);
+    temp.put(SystemQueryOptionKind.APPLY, 11);
     OPTION_INDEX = Collections.unmodifiableMap(temp);
   }
 
@@ -255,7 +256,6 @@ public class UriValidator {
 
   private UriType getUriTypeForFunction(final UriResource lastPathSegment) throws UriValidationException {
     final UriResourceFunction uriFunction = (UriResourceFunction) lastPathSegment;
-
     final boolean isCollection = uriFunction.isCollection();
     final EdmTypeKind typeKind = uriFunction.getFunction().getReturnType().getType().getKind();
     UriType uriType;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
index 3b63c1b..985bfe6 100644
--- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
+++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
@@ -73,6 +73,9 @@ UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is n
 UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments; found: '%1$s'.
 UriParserSemanticException.TYPES_NOT_COMPATIBLE=The types '%1$s' and '%2$s' are not compatible.
 UriParserSemanticException.NOT_A_MEDIA_RESOURCE=The resource '%1$s' is not a media resource. $value can only be applied on media resources.
+UriParserSemanticException.IS_PROPERTY=The identifier '%1$s' is already used as a property.
+UriParserSemanticException.ONLY_FOR_PRIMITIVE_TYPES='%1$s' is only allowed for primitive-type expressions.
+UriParserSemanticException.FUNCTION_MUST_USE_COLLECTIONS=Only bound functions with collections of structural types as binding parameter and as return type are allowed; '%1$s' is not such a function.
 
 UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported.
 UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported.

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
index 118e2ea..23bf1d1 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
@@ -97,6 +97,18 @@ public class UriTokenizerTest {
   }
 
   @Test
+  public void saveState() {
+    UriTokenizer tokenizer = new UriTokenizer("a*");
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    tokenizer.saveState();
+    assertTrue(tokenizer.next(TokenKind.STAR));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+    tokenizer.returnToSavedState();
+    assertTrue(tokenizer.next(TokenKind.STAR));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+  }
+
+  @Test
   public void systemQueryOptions() {
     UriTokenizer tokenizer = new UriTokenizer("$expand=*;$filter=true;$levels=max;$orderby=false");
     assertTrue(tokenizer.next(TokenKind.EXPAND));
@@ -642,6 +654,64 @@ public class UriTokenizerTest {
     wrongToken(TokenKind.GeometryCollection, "geometry'SRID=0;Collection(Point(1 2),Point(3 4))'", 'x');
   }
 
+  @Test
+  public void aggregation() {
+    UriTokenizer tokenizer = new UriTokenizer("$apply=aggregate(a with sum as s from x with average)");
+    assertTrue(tokenizer.next(TokenKind.APPLY));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.AggregateTrafo));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.WithOperator));
+    assertTrue(tokenizer.next(TokenKind.SUM));
+    assertTrue(tokenizer.next(TokenKind.AsOperator));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.FromOperator));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.WithOperator));
+    assertTrue(tokenizer.next(TokenKind.AVERAGE));
+    assertTrue(tokenizer.next(TokenKind.CLOSE));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+
+    tokenizer = new UriTokenizer("a with min as m");
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.WithOperator));
+    assertTrue(tokenizer.next(TokenKind.MIN));
+
+    tokenizer = new UriTokenizer("a with countdistinct as c");
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.WithOperator));
+    assertTrue(tokenizer.next(TokenKind.COUNTDISTINCT));
+
+    assertTrue(new UriTokenizer("identity").next(TokenKind.IDENTITY));
+    assertTrue(new UriTokenizer("bottomcount(1,x)").next(TokenKind.BottomCountTrafo));
+    assertTrue(new UriTokenizer("bottompercent(1,x)").next(TokenKind.BottomPercentTrafo));
+    assertTrue(new UriTokenizer("bottomsum(1,x)").next(TokenKind.BottomSumTrafo));
+    assertTrue(new UriTokenizer("topcount(1,x)").next(TokenKind.TopCountTrafo));
+    assertTrue(new UriTokenizer("toppercent(1,x)").next(TokenKind.TopPercentTrafo));
+    assertTrue(new UriTokenizer("topsum(1,x)").next(TokenKind.TopSumTrafo));
+    assertTrue(new UriTokenizer("compute(a mul b as m)").next(TokenKind.ComputeTrafo));
+
+    assertTrue(new UriTokenizer("search(a)").next(TokenKind.SearchTrafo));
+    assertTrue(new UriTokenizer("expand(a)").next(TokenKind.ExpandTrafo));
+    assertTrue(new UriTokenizer("filter(true)").next(TokenKind.FilterTrafo));
+
+    tokenizer = new UriTokenizer("groupby((rollup($all,x,y)))");
+    assertTrue(tokenizer.next(TokenKind.GroupByTrafo));
+    assertTrue(tokenizer.next(TokenKind.OPEN));
+    assertTrue(tokenizer.next(TokenKind.RollUpSpec));
+    assertTrue(tokenizer.next(TokenKind.ROLLUP_ALL));
+    assertTrue(tokenizer.next(TokenKind.COMMA));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.COMMA));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.CLOSE));
+    assertTrue(tokenizer.next(TokenKind.CLOSE));
+    assertTrue(tokenizer.next(TokenKind.CLOSE));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+
+    assertTrue(new UriTokenizer("isdefined(x)").next(TokenKind.IsDefinedMethod));
+  }
+
   private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) {
     assertFalse(new UriTokenizer(disturbCharacter + value).next(kind));
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index 28cf63d..ddbfb1a 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@ -233,7 +233,7 @@ public abstract class TechnicalProcessor implements Processor {
   }
 
   protected void validateOptions(final UriInfoResource uriInfo) throws ODataApplicationException {
-    if (uriInfo.getIdOption() != null) {
+    if (uriInfo.getIdOption() != null || uriInfo.getApplyOption() != null) {
       throw new ODataApplicationException("Not all of the specified options are supported.",
           HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
     }