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);
}