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:51 UTC

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

[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;
+  }
+}