You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2016/04/18 15:07:51 UTC
[3/3] olingo-odata4 git commit: [OLINGO-935]URI-parser support of the
data aggregation extension
[OLINGO-935]URI-parser support of the data aggregation extension
Signed-off-by: Christian Amend <ch...@sap.com>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/6553e950
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/6553e950
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/6553e950
Branch: refs/heads/master
Commit: 6553e95080b66b80f1e9e5e871f941d2164060c1
Parents: 824c174
Author: Klaus Straubinger <kl...@sap.com>
Authored: Fri Apr 15 16:11:30 2016 +0200
Committer: Christian Amend <ch...@sap.com>
Committed: Mon Apr 18 15:06:33 2016 +0200
----------------------------------------------------------------------
.../olingo/server/api/uri/UriInfoResource.java | 6 +
.../server/api/uri/queryoption/ApplyItem.java | 45 ++
.../server/api/uri/queryoption/ApplyOption.java | 32 +
.../uri/queryoption/SystemQueryOptionKind.java | 7 +-
.../api/uri/queryoption/apply/Aggregate.java | 35 +
.../queryoption/apply/AggregateExpression.java | 80 +++
.../api/uri/queryoption/apply/BottomTop.java | 51 ++
.../api/uri/queryoption/apply/Compute.java | 35 +
.../queryoption/apply/ComputeExpression.java | 40 ++
.../api/uri/queryoption/apply/Concat.java | 36 +
.../uri/queryoption/apply/CustomFunction.java | 43 ++
.../api/uri/queryoption/apply/Expand.java | 34 +
.../api/uri/queryoption/apply/Filter.java | 34 +
.../api/uri/queryoption/apply/GroupBy.java | 42 ++
.../api/uri/queryoption/apply/GroupByItem.java | 48 ++
.../api/uri/queryoption/apply/Identity.java | 27 +
.../api/uri/queryoption/apply/Search.java | 34 +
.../olingo/server/core/debug/DebugTabUri.java | 191 +++++-
.../core/debug/ExpressionJsonVisitor.java | 54 +-
.../olingo/server/core/uri/UriInfoImpl.java | 12 +-
.../server/core/uri/parser/ApplyParser.java | 570 ++++++++++++++++
.../olingo/server/core/uri/parser/Parser.java | 31 +-
.../uri/parser/UriParserSemanticException.java | 12 +-
.../server/core/uri/parser/UriTokenizer.java | 135 +++-
.../core/uri/queryoption/ApplyOptionImpl.java | 46 ++
.../apply/AggregateExpressionImpl.java | 113 ++++
.../uri/queryoption/apply/AggregateImpl.java | 48 ++
.../uri/queryoption/apply/BottomTopImpl.java | 69 ++
.../apply/ComputeExpressionImpl.java | 51 ++
.../core/uri/queryoption/apply/ComputeImpl.java | 48 ++
.../core/uri/queryoption/apply/ConcatImpl.java | 48 ++
.../queryoption/apply/CustomFunctionImpl.java | 62 ++
.../uri/queryoption/apply/DynamicProperty.java | 118 ++++
.../apply/DynamicStructuredType.java | 141 ++++
.../core/uri/queryoption/apply/ExpandImpl.java | 45 ++
.../core/uri/queryoption/apply/FilterImpl.java | 45 ++
.../core/uri/queryoption/apply/GroupByImpl.java | 60 ++
.../uri/queryoption/apply/GroupByItemImpl.java | 67 ++
.../uri/queryoption/apply/IdentityImpl.java | 32 +
.../core/uri/queryoption/apply/SearchImpl.java | 45 ++
.../server/core/uri/validator/UriValidator.java | 44 +-
.../server-core-exceptions-i18n.properties | 3 +
.../core/uri/parser/UriTokenizerTest.java | 70 ++
.../tecsvc/processor/TechnicalProcessor.java | 2 +-
.../server/core/uri/parser/ApplyParserTest.java | 676 +++++++++++++++++++
.../core/uri/validator/UriValidatorTest.java | 57 +-
46 files changed, 3416 insertions(+), 108 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
index 0550eae..05e010e 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriInfoResource.java
@@ -20,6 +20,7 @@ package org.apache.olingo.server.api.uri;
import java.util.List;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.CountOption;
import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -100,6 +101,11 @@ public interface UriInfoResource {
TopOption getTopOption();
/**
+ * @return information about the $apply option
+ */
+ ApplyOption getApplyOption();
+
+ /**
* The path segments behind the service root define which resources are
* requested by that URI. This may be entities/functions/actions and more.
* Each segments information (name, key predicates, function parameters, ...) is
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
new file mode 100644
index 0000000..34588e2
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyItem.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption;
+
+/**
+ * Represents a single transformation from the system query option $apply.
+ */
+public interface ApplyItem {
+
+ /** The kind of the transformation. */
+ public enum Kind {
+ AGGREGATE,
+ BOTTOM_TOP,
+ COMPUTE,
+ CONCAT,
+ CUSTOM_FUNCTION,
+ EXPAND,
+ FILTER,
+ GROUP_BY,
+ IDENTITY,
+ SEARCH
+ }
+
+ /**
+ * Gets the kind of the transformation.
+ * @return transformation kind
+ */
+ Kind getKind();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
new file mode 100644
index 0000000..e5b10b5
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ApplyOption.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption;
+
+import java.util.List;
+
+/**
+ * Represents the system query option $apply, defined in the data aggregation extension.
+ */
+public interface ApplyOption extends SystemQueryOption {
+
+ /**
+ * @return a list of transformations
+ */
+ List<ApplyItem> getApplyItems();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
index d5d60a1..3cfe47f 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java
@@ -82,7 +82,12 @@ public enum SystemQueryOptionKind {
/**
* @see LevelsExpandOption
*/
- LEVELS("$levels");
+ LEVELS("$levels"),
+
+ /**
+ * @see ApplyOption
+ */
+ APPLY("$apply");
private final String syntax;
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
new file mode 100644
index 0000000..d589aed
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Aggregate.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the aggregate transformation.
+ */
+public interface Aggregate extends ApplyItem {
+
+ /**
+ * Gets the aggregate expressions.
+ * @return a non-empty list of aggregate expressions (and never <code>null</code>)
+ */
+ List<AggregateExpression> getExpressions();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
new file mode 100644
index 0000000..e297a22
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/AggregateExpression.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents an aggregate expression.
+ * @see Aggregate
+ */
+public interface AggregateExpression {
+
+ /** Standard aggregation method. */
+ public enum StandardMethod { SUM, MIN, MAX, AVERAGE, COUNT_DISTINCT }
+
+ /**
+ * Gets the path prefix and the path segment.
+ * @return a (potentially empty) list of path segments (and never <code>null</code>)
+ */
+ List<UriResource> getPath();
+
+ /**
+ * Gets the common expression to be aggregated.
+ * @return an {@link Expression} that could be <code>null</code>
+ */
+ Expression getExpression();
+
+ /**
+ * Gets the standard aggregation method if used.
+ * @return a {@link StandardMethod} or <code>null</code>
+ * @see #getCustomMethod()
+ */
+ StandardMethod getStandardMethod();
+
+ /**
+ * Gets the name of the custom aggregation method if used.
+ * @return a {@link FullQualifiedName} or <code>null</code>
+ * @see #getStandardMethod()
+ */
+ FullQualifiedName getCustomMethod();
+
+ /**
+ * Gets the name of the aggregate if an alias name has been set.
+ * @return an identifier String or <code>null</code>
+ */
+ String getAlias();
+
+ /**
+ * Gets the inline aggregation expression to be applied to the target of the path if used.
+ * @return an aggregation expression or <code>null</code>
+ * @see #getPath()
+ */
+ AggregateExpression getInlineAggregateExpression();
+
+ /**
+ * Gets the aggregate expressions for <code>from</code>.
+ * @return a (potentially empty) list of aggregate expressions (but never <code>null</code>)
+ */
+ List<AggregateExpression> getFrom();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
new file mode 100644
index 0000000..5249c4b
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/BottomTop.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents a transformation with one of the pre-defined methods
+ * <code>bottomcount</code>, <code>bottompercent</code>, <code>bottomsum</code>,
+ * <code>topcount</code>, <code>toppercent</code>, <code>topsum</code>.
+ */
+public interface BottomTop extends ApplyItem {
+
+ /** Pre-defined method for partial aggregration. */
+ public enum Method { BOTTOM_COUNT, BOTTOM_PERCENT, BOTTOM_SUM, TOP_COUNT, TOP_PERCENT, TOP_SUM }
+
+ /**
+ * Gets the partial-aggregation method.
+ * @return a {@link Method} (but never <code>null</code>)
+ */
+ Method getMethod();
+
+ /**
+ * Gets the expression that determines the number of items to aggregate.
+ * @return an {@link Expression} (but never <code>null</code>)
+ */
+ Expression getNumber();
+
+ /**
+ * Gets the expression that determines the values to aggregate.
+ * @return an {@link Expression} (but never <code>null</code>)
+ */
+ Expression getValue();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
new file mode 100644
index 0000000..addb503
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Compute.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the compute transformation.
+ */
+public interface Compute extends ApplyItem {
+
+ /**
+ * Gets the compute expressions.
+ * @return a non-empty list of compute expressions (and never <code>null</code>)
+ */
+ List<ComputeExpression> getExpressions();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
new file mode 100644
index 0000000..c65bb83
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/ComputeExpression.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+
+/**
+ * Represents a compute expression.
+ * @see Compute
+ */
+public interface ComputeExpression {
+
+ /**
+ * Gets the expression to compute.
+ * @return an {@link Expression} (but never <code>null</code>)
+ */
+ Expression getExpression();
+
+ /**
+ * Gets the name of the computation result if an alias name has been set.
+ * @return an identifier String (but never <code>null</code>)
+ */
+ String getAlias();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
new file mode 100644
index 0000000..d3fe6c8
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Concat.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+
+/**
+ * Represents the concat transformation.
+ */
+public interface Concat extends ApplyItem {
+
+ /**
+ * Gets the concatenated apply options.
+ * @return a non-empty list of apply options (and never <code>null</code>)
+ */
+ List<ApplyOption> getApplyOptions();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
new file mode 100644
index 0000000..d3db32f
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/CustomFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.edm.EdmFunction;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents a transformation with a custom function.
+ */
+public interface CustomFunction extends ApplyItem {
+
+ /**
+ * Gets the function to use.
+ * @return an {@link EdmFunction} (but never <code>null</code>)
+ */
+ EdmFunction getFunction();
+
+ /**
+ * Gets the function parameters.
+ * @return a (potentially empty) list of parameters (but never <code>null</code>)
+ */
+ List<UriParameter> getParameters();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
new file mode 100644
index 0000000..9bab8ea
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Expand.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
+
+/**
+ * Represents the expand transformation.
+ */
+public interface Expand extends ApplyItem {
+
+ /**
+ * Gets the expand option.
+ * @return an {@link ExpandOption} (but never <code>null</code>)
+ */
+ ExpandOption getExpandOption();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
new file mode 100644
index 0000000..2989950
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Filter.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.FilterOption;
+
+/**
+ * Represents the filter transformation.
+ */
+public interface Filter extends ApplyItem {
+
+ /**
+ * Gets the filter option.
+ * @return a {@link FilterOption} (but never <code>null</code>)
+ */
+ FilterOption getFilterOption();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
new file mode 100644
index 0000000..5594b76
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupBy.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+
+/**
+ * Represents the grouping transformation.
+ */
+public interface GroupBy extends ApplyItem {
+
+ /**
+ * Gets the items to group.
+ * @return a non-empty list of {@link GroupByItem}s (but never <code>null</code>)
+ */
+ List<GroupByItem> getGroupByItems();
+
+ /**
+ * Gets the apply option to be applied to the grouped items.
+ * @return an {@link ApplyOption} (but never <code>null</code>)
+ */
+ ApplyOption getApplyOption();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
new file mode 100644
index 0000000..f4738ec
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/GroupByItem.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.uri.UriResource;
+
+/**
+ * Represents a grouping property.
+ * @see GroupBy
+ */
+public interface GroupByItem {
+
+ /**
+ * Gets the path.
+ * @return a (potentially empty) list of path segments (and never <code>null</code>)
+ */
+ List<UriResource> getPath();
+
+ /**
+ * Whether a nested rollup clause contains the special value '$all'.
+ * @return <code>true</code> if '$all' has been given in rollup, <code>false</code> otherwise
+ */
+ boolean isRollupAll();
+
+ /**
+ * Gets the rollup.
+ * @return a (potentially empty) list of grouping items (and never <code>null</code>)
+ */
+ List<GroupByItem> getRollup();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
new file mode 100644
index 0000000..fbe050a
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Identity.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+
+/**
+ * Represents the identity transformation.
+ */
+public interface Identity extends ApplyItem {
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
new file mode 100644
index 0000000..c0d8362
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/apply/Search.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.uri.queryoption.apply;
+
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+
+/**
+ * Represents the search transformation.
+ */
+public interface Search extends ApplyItem {
+
+ /**
+ * Gets the search option.
+ * @return a {@link SearchOption} (but never <code>null</code>)
+ */
+ SearchOption getSearchOption();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
index 842b8f5..05ac2aa 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/DebugTabUri.java
@@ -34,6 +34,8 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.CountOption;
import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -46,6 +48,18 @@ import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import org.apache.olingo.server.api.uri.queryoption.SkipOption;
import org.apache.olingo.server.api.uri.queryoption.TopOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
+import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+import org.apache.olingo.server.api.uri.queryoption.apply.Search;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
@@ -94,7 +108,7 @@ public class DebugTabUri implements DebugTab {
appendCommonJsonObjects(gen, uriInfo.getCountOption(), uriInfo.getSkipOption(), uriInfo.getTopOption(),
uriInfo.getFilterOption(), uriInfo.getOrderByOption(), uriInfo.getSelectOption(), uriInfo.getExpandOption(),
- uriInfo.getSearchOption());
+ uriInfo.getSearchOption(), uriInfo.getApplyOption());
if (!uriInfo.getAliases().isEmpty()) {
gen.writeFieldName("aliases");
@@ -109,12 +123,12 @@ public class DebugTabUri implements DebugTab {
gen.writeEndObject();
}
- private void appendCommonJsonObjects(final JsonGenerator gen, final CountOption countOption,
- final SkipOption skipOption,
- final TopOption topOption, final FilterOption filterOption, final OrderByOption orderByOption,
- final SelectOption selectOption,
- final ExpandOption expandOption, final SearchOption searchOption)
- throws IOException {
+ private void appendCommonJsonObjects(JsonGenerator gen,
+ final CountOption countOption, final SkipOption skipOption, final TopOption topOption,
+ final FilterOption filterOption, final OrderByOption orderByOption,
+ final SelectOption selectOption, final ExpandOption expandOption, final SearchOption searchOption,
+ final ApplyOption applyOption)
+ throws IOException {
if (countOption != null) {
gen.writeBooleanField("isCount", countOption.getValue());
}
@@ -155,6 +169,11 @@ public class DebugTabUri implements DebugTab {
gen.writeFieldName("search");
appendSearchJson(gen, searchOption.getSearchExpression());
}
+
+ if (applyOption != null) {
+ gen.writeFieldName("apply");
+ appendApplyItemsJson(gen, applyOption.getApplyItems());
+ }
}
private void appendURIResourceParts(final JsonGenerator gen, final List<UriResource> uriResourceParts)
@@ -223,14 +242,7 @@ public class DebugTabUri implements DebugTab {
gen.writeBooleanField("star", item.isStar());
} else if (item.getResourcePath() != null && !item.getResourcePath().getUriResourceParts().isEmpty()) {
gen.writeFieldName("expandPath");
- gen.writeStartArray();
- for (UriResource resource : item.getResourcePath().getUriResourceParts()) {
- gen.writeStartObject();
- gen.writeStringField("propertyKind", resource.getKind().toString());
- gen.writeStringField("propertyName", resource.toString());
- gen.writeEndObject();
- }
- gen.writeEndArray();
+ appendURIResourceParts(gen, item.getResourcePath().getUriResourceParts());
}
if (item.isRef()) {
@@ -240,7 +252,7 @@ public class DebugTabUri implements DebugTab {
if (item.getLevelsOption() != null) {
gen.writeFieldName("levels");
if (item.getLevelsOption().isMax()) {
- gen.writeString("max");
+ gen.writeString("max");
} else {
gen.writeNumber(item.getLevelsOption().getValue());
}
@@ -248,7 +260,7 @@ public class DebugTabUri implements DebugTab {
appendCommonJsonObjects(gen, item.getCountOption(), item.getSkipOption(), item.getTopOption(),
item.getFilterOption(), item.getOrderByOption(), item.getSelectOption(), item.getExpandOption(),
- item.getSearchOption());
+ item.getSearchOption(), null); // TODO: item.getApplyOption()
gen.writeEndObject();
}
@@ -315,6 +327,142 @@ public class DebugTabUri implements DebugTab {
json.writeEndObject();
}
+ private void appendApplyItemsJson(JsonGenerator json, final List<ApplyItem> applyItems) throws IOException {
+ json.writeStartArray();
+ for (final ApplyItem item : applyItems) {
+ appendApplyItemJson(json, item);
+ }
+ json.writeEndArray();
+ }
+
+ private void appendApplyItemJson(JsonGenerator json, final ApplyItem item) throws IOException {
+ json.writeStartObject();
+
+ json.writeStringField("kind", item.getKind().name());
+ switch (item.getKind()) {
+ case AGGREGATE:
+ appendAggregateJson(json, (Aggregate) item);
+ break;
+ case BOTTOM_TOP:
+ json.writeStringField("method", ((BottomTop) item).getMethod().name());
+ json.writeFieldName("number");
+ appendExpressionJson(json, ((BottomTop) item).getNumber());
+ json.writeFieldName("value");
+ appendExpressionJson(json, ((BottomTop) item).getValue());
+ break;
+ case COMPUTE:
+ json.writeFieldName("compute");
+ json.writeStartArray();
+ for (final ComputeExpression computeExpression : ((Compute) item).getExpressions()) {
+ json.writeStartObject();
+ json.writeFieldName("expression");
+ appendExpressionJson(json, computeExpression.getExpression());
+ json.writeStringField("as", computeExpression.getAlias());
+ json.writeEndObject();
+ }
+ json.writeEndArray();
+ break;
+ case CONCAT:
+ json.writeFieldName("concat");
+ json.writeStartArray();
+ for (final ApplyOption option : ((Concat) item).getApplyOptions()) {
+ appendApplyItemsJson(json, option.getApplyItems());
+ }
+ json.writeEndArray();
+ break;
+ case CUSTOM_FUNCTION:
+ json.writeStringField("name",
+ ((CustomFunction) item).getFunction().getFullQualifiedName().getFullQualifiedNameAsString());
+ appendParameters(json, "parameters", ((CustomFunction) item).getParameters());
+ break;
+ case EXPAND:
+ appendCommonJsonObjects(json, null, null, null, null, null, null, ((Expand) item).getExpandOption(), null, null);
+ break;
+ case FILTER:
+ appendCommonJsonObjects(json, null, null, null, ((Filter) item).getFilterOption(), null, null, null, null, null);
+ break;
+ case GROUP_BY:
+ json.writeFieldName("groupBy");
+ appendGroupByItemsJson(json, ((GroupBy) item).getGroupByItems());
+ appendCommonJsonObjects(json, null, null, null, null, null, null, null, null, ((GroupBy) item).getApplyOption());
+ break;
+ case IDENTITY:
+ break;
+ case SEARCH:
+ appendCommonJsonObjects(json, null, null, null, null, null, null, null, ((Search) item).getSearchOption(), null);
+ break;
+ }
+
+ json.writeEndObject();
+ }
+
+ private void appendGroupByItemsJson(JsonGenerator json, final List<GroupByItem> groupByItems) throws IOException {
+ json.writeStartArray();
+ for (final GroupByItem groupByItem : groupByItems) {
+ json.writeStartObject();
+ if (!groupByItem.getPath().isEmpty()) {
+ json.writeFieldName("path");
+ appendURIResourceParts(json, groupByItem.getPath());
+ }
+ json.writeBooleanField("isRollupAll", groupByItem.isRollupAll());
+ if (!groupByItem.getRollup().isEmpty()) {
+ json.writeFieldName("rollup");
+ appendGroupByItemsJson(json, groupByItem.getRollup());
+ }
+ json.writeEndObject();
+ }
+ json.writeEndArray();
+ }
+
+ private void appendAggregateJson(JsonGenerator json, final Aggregate aggregate) throws IOException {
+ json.writeFieldName("aggregate");
+ appendAggregateExpressionsJson(json, aggregate.getExpressions());
+ }
+
+ private void appendAggregateExpressionsJson(JsonGenerator json, final List<AggregateExpression> aggregateExpressions)
+ throws IOException {
+ json.writeStartArray();
+ for (final AggregateExpression aggregateExpression : aggregateExpressions) {
+ appendAggregateExpressionJson(json, aggregateExpression);
+ }
+ json.writeEndArray();
+ }
+
+ private void appendAggregateExpressionJson(JsonGenerator json, final AggregateExpression aggregateExpression)
+ throws IOException {
+ if (aggregateExpression == null) {
+ json.writeNull();
+ } else {
+ json.writeStartObject();
+ if (!aggregateExpression.getPath().isEmpty()) {
+ json.writeFieldName("path");
+ appendURIResourceParts(json, aggregateExpression.getPath());
+ }
+ if (aggregateExpression.getExpression() != null) {
+ json.writeFieldName("expression");
+ appendExpressionJson(json, aggregateExpression.getExpression());
+ }
+ if (aggregateExpression.getStandardMethod() != null) {
+ json.writeStringField("standardMethod", aggregateExpression.getStandardMethod().name());
+ }
+ if (aggregateExpression.getCustomMethod() != null) {
+ json.writeStringField("customMethod", aggregateExpression.getCustomMethod().getFullQualifiedNameAsString());
+ }
+ if (aggregateExpression.getAlias() != null) {
+ json.writeStringField("as", aggregateExpression.getAlias());
+ }
+ if (aggregateExpression.getInlineAggregateExpression() != null) {
+ json.writeFieldName("inlineAggregateExpression");
+ appendAggregateExpressionJson(json, aggregateExpression.getInlineAggregateExpression());
+ }
+ if (!aggregateExpression.getFrom().isEmpty()) {
+ json.writeFieldName("from");
+ appendAggregateExpressionsJson(json, aggregateExpression.getFrom());
+ }
+ json.writeEndObject();
+ }
+ }
+
@Override
public void appendHtml(final Writer writer) throws IOException {
// factory for JSON generators (the object mapper is necessary to write expression trees)
@@ -377,6 +525,15 @@ public class DebugTabUri implements DebugTab {
writer.append("</ul>\n");
}
+ if (uriInfo.getApplyOption() != null) {
+ writer.append("<h2>Apply Option</h2>\n")
+ .append("<ul>\n<li class=\"json\">");
+ json = jsonFactory.createGenerator(writer).useDefaultPrettyPrinter();
+ appendApplyItemsJson(json, uriInfo.getApplyOption().getApplyItems());
+ json.close();
+ writer.append("\n</li>\n</ul>\n");
+ }
+
if (uriInfo.getCountOption() != null
|| uriInfo.getSkipOption() != null
|| uriInfo.getSkipTokenOption() != null
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
index a8b22e9..b93bb3f 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/debug/ExpressionJsonVisitor.java
@@ -129,31 +129,28 @@ public class ExpressionJsonVisitor implements ExpressionVisitor<JsonNode> {
public JsonNode visitMember(final Member member)
throws ExpressionVisitException, ODataApplicationException {
final List<UriResource> uriResourceParts = member.getResourcePath().getUriResourceParts();
+ final UriResource lastSegment = uriResourceParts.get(uriResourceParts.size() - 1);
ObjectNode result = nodeFactory.objectNode()
.put(NODE_TYPE_NAME, MEMBER_NAME)
- .put(TYPE_NAME, getType(uriResourceParts));
+ .put(TYPE_NAME, getType(lastSegment));
ArrayNode segments = result.putArray(RESOURCE_SEGMENTS_NAME);
- if (uriResourceParts != null) {
- for (final UriResource segment : uriResourceParts) {
- if (segment instanceof UriResourceLambdaAll) {
- final UriResourceLambdaAll all = (UriResourceLambdaAll) segment;
- segments.add(visitLambdaExpression(ALL_NAME, all.getLambdaVariable(), all.getExpression()));
- } else if (segment instanceof UriResourceLambdaAny) {
- final UriResourceLambdaAny any = (UriResourceLambdaAny) segment;
- segments.add(visitLambdaExpression(ANY_NAME, any.getLambdaVariable(), any.getExpression()));
- } else if (segment instanceof UriResourcePartTyped) {
- final String typeName = ((UriResourcePartTyped) segment).getType()
- .getFullQualifiedName().getFullQualifiedNameAsString();
- segments.add(nodeFactory.objectNode()
- .put(NODE_TYPE_NAME, segment.getKind().toString())
- .put(NAME_NAME, segment.toString())
- .put(TYPE_NAME, typeName));
- } else {
- segments.add(nodeFactory.objectNode()
- .put(NODE_TYPE_NAME, segment.getKind().toString())
- .put(NAME_NAME, segment.toString())
- .putNull(TYPE_NAME));
- }
+ for (final UriResource segment : uriResourceParts) {
+ if (segment instanceof UriResourceLambdaAll) {
+ final UriResourceLambdaAll all = (UriResourceLambdaAll) segment;
+ segments.add(visitLambdaExpression(ALL_NAME, all.getLambdaVariable(), all.getExpression()));
+ } else if (segment instanceof UriResourceLambdaAny) {
+ final UriResourceLambdaAny any = (UriResourceLambdaAny) segment;
+ segments.add(visitLambdaExpression(ANY_NAME, any.getLambdaVariable(), any.getExpression()));
+ } else if (segment instanceof UriResourcePartTyped) {
+ segments.add(nodeFactory.objectNode()
+ .put(NODE_TYPE_NAME, segment.getKind().toString())
+ .put(NAME_NAME, segment.toString())
+ .put(TYPE_NAME, getType(segment)));
+ } else {
+ segments.add(nodeFactory.objectNode()
+ .put(NODE_TYPE_NAME, segment.getKind().toString())
+ .put(NAME_NAME, segment.toString())
+ .putNull(TYPE_NAME));
}
}
return result;
@@ -287,15 +284,8 @@ public class ExpressionJsonVisitor implements ExpressionVisitor<JsonNode> {
return type == null ? null : type.getFullQualifiedName().getFullQualifiedNameAsString();
}
- private String getType(final List<UriResource> uriResourceParts) {
- if (uriResourceParts == null || uriResourceParts.isEmpty()) {
- return null;
- }
- final UriResource lastSegment = uriResourceParts.get(uriResourceParts.size() - 1);
- final EdmType type = lastSegment instanceof UriResourcePartTyped ?
- ((UriResourcePartTyped) lastSegment).getType() :
- null;
- return type == null ? UNKNOWN_NAME : type.getFullQualifiedName().getFullQualifiedNameAsString();
+ private String getType(final UriResource segment) {
+ final EdmType type = segment instanceof UriResourcePartTyped ? ((UriResourcePartTyped) segment).getType() : null;
+ return type == null ? UNKNOWN_NAME : type.getFullQualifiedName().getFullQualifiedNameAsString();
}
-
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
index 916307f..e034e8b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java
@@ -38,6 +38,7 @@ import org.apache.olingo.server.api.uri.UriInfoResource;
import org.apache.olingo.server.api.uri.UriInfoService;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.CountOption;
import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
@@ -144,11 +145,6 @@ public class UriInfoImpl implements UriInfo {
return this;
}
- public UriInfoImpl removeResourcePart(final int index) {
- pathParts.remove(index);
- return this;
- }
-
public UriResource getLastResourcePart() {
return lastResourcePart;
}
@@ -195,6 +191,7 @@ public class UriInfoImpl implements UriInfo {
case SKIPTOKEN:
case TOP:
case LEVELS:
+ case APPLY:
systemQueryOptions.put(systemQueryOptionKind, systemOption);
break;
default:
@@ -259,6 +256,11 @@ public class UriInfoImpl implements UriInfo {
}
@Override
+ public ApplyOption getApplyOption() {
+ return (ApplyOption) systemQueryOptions.get(SystemQueryOptionKind.APPLY);
+ }
+
+ @Override
public List<SystemQueryOption> getSystemQueryOptions() {
return Collections.unmodifiableList(new ArrayList<SystemQueryOption>(systemQueryOptions.values()));
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6553e950/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
new file mode 100644
index 0000000..9872eb1
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ApplyParser.java
@@ -0,0 +1,570 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.uri.parser;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmElement;
+import org.apache.olingo.commons.api.edm.EdmFunction;
+import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
+import org.apache.olingo.commons.api.edm.EdmParameter;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.EdmProperty;
+import org.apache.olingo.commons.api.edm.EdmReturnType;
+import org.apache.olingo.commons.api.edm.EdmStructuredType;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceKind;
+import org.apache.olingo.server.api.uri.UriResourcePartTyped;
+import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
+import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
+import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
+import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
+import org.apache.olingo.server.api.uri.queryoption.FilterOption;
+import org.apache.olingo.server.api.uri.queryoption.SearchOption;
+import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
+import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
+import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
+import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
+import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
+import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
+import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
+import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
+import org.apache.olingo.server.core.uri.UriInfoImpl;
+import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
+import org.apache.olingo.server.core.uri.UriResourceCountImpl;
+import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl;
+import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl;
+import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
+import org.apache.olingo.server.core.uri.queryoption.ApplyOptionImpl;
+import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl;
+import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.AggregateExpressionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.AggregateImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.BottomTopImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ComputeExpressionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ComputeImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.ConcatImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.CustomFunctionImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.DynamicProperty;
+import org.apache.olingo.server.core.uri.queryoption.apply.DynamicStructuredType;
+import org.apache.olingo.server.core.uri.queryoption.apply.ExpandImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.FilterImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.GroupByImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.GroupByItemImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.IdentityImpl;
+import org.apache.olingo.server.core.uri.queryoption.apply.SearchImpl;
+import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl;
+import org.apache.olingo.server.core.uri.validator.UriValidationException;
+
+public class ApplyParser {
+
+ private static final Map<TokenKind, StandardMethod> TOKEN_KIND_TO_STANDARD_METHOD;
+ static {
+ Map<TokenKind, StandardMethod> temp = new EnumMap<TokenKind, StandardMethod>(TokenKind.class);
+ temp.put(TokenKind.SUM, StandardMethod.SUM);
+ temp.put(TokenKind.MIN, StandardMethod.MIN);
+ temp.put(TokenKind.MAX, StandardMethod.MAX);
+ temp.put(TokenKind.AVERAGE, StandardMethod.AVERAGE);
+ temp.put(TokenKind.COUNTDISTINCT, StandardMethod.COUNT_DISTINCT);
+ TOKEN_KIND_TO_STANDARD_METHOD = Collections.unmodifiableMap(temp);
+ }
+
+ private static final Map<TokenKind, BottomTop.Method> TOKEN_KIND_TO_BOTTOM_TOP_METHOD;
+ static {
+ Map<TokenKind, BottomTop.Method> temp = new EnumMap<TokenKind, BottomTop.Method>(TokenKind.class);
+ temp.put(TokenKind.BottomCountTrafo, BottomTop.Method.BOTTOM_COUNT);
+ temp.put(TokenKind.BottomPercentTrafo, BottomTop.Method.BOTTOM_PERCENT);
+ temp.put(TokenKind.BottomSumTrafo, BottomTop.Method.BOTTOM_SUM);
+ temp.put(TokenKind.TopCountTrafo, BottomTop.Method.TOP_COUNT);
+ temp.put(TokenKind.TopPercentTrafo, BottomTop.Method.TOP_PERCENT);
+ temp.put(TokenKind.TopSumTrafo, BottomTop.Method.TOP_SUM);
+ TOKEN_KIND_TO_BOTTOM_TOP_METHOD = Collections.unmodifiableMap(temp);
+ }
+
+ private final Edm edm;
+ private final OData odata;
+
+ private UriTokenizer tokenizer;
+ private Collection<String> crossjoinEntitySetNames;
+ private Map<String, AliasQueryOption> aliases;
+
+ public ApplyParser(final Edm edm, final OData odata) {
+ this.edm = edm;
+ this.odata = odata;
+ }
+
+ public ApplyOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType,
+ final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases)
+ throws UriParserException, UriValidationException {
+ this.tokenizer = tokenizer;
+ this.crossjoinEntitySetNames = crossjoinEntitySetNames;
+ this.aliases = aliases;
+
+ // TODO: Check when to create a new dynamic type and how it can be returned.
+ DynamicStructuredType type = new DynamicStructuredType(referencedType);
+ return parseApply(type);
+ }
+
+ private ApplyOption parseApply(EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ ApplyOptionImpl option = new ApplyOptionImpl();
+ do {
+ option.add(parseTrafo(referencedType));
+ } while (tokenizer.next(TokenKind.SLASH));
+ return option;
+ }
+
+ private ApplyItem parseTrafo(EdmStructuredType referencedType) throws UriParserException, UriValidationException {
+ if (tokenizer.next(TokenKind.AggregateTrafo)) {
+ return parseAggregateTrafo(referencedType);
+
+ } else if (tokenizer.next(TokenKind.IDENTITY)) {
+ return new IdentityImpl();
+
+ } else if (tokenizer.next(TokenKind.ComputeTrafo)) {
+ return parseComputeTrafo(referencedType);
+
+ } else if (tokenizer.next(TokenKind.ConcatMethod)) {
+ return parseConcatTrafo(referencedType);
+
+ } else if (tokenizer.next(TokenKind.ExpandTrafo)) {
+ return new ExpandImpl().setExpandOption(parseExpandTrafo(referencedType));
+
+ } else if (tokenizer.next(TokenKind.FilterTrafo)) {
+ final FilterOption filterOption = new FilterParser(edm, odata)
+ .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return new FilterImpl().setFilterOption(filterOption);
+
+ } else if (tokenizer.next(TokenKind.GroupByTrafo)) {
+ return parseGroupByTrafo(referencedType);
+
+ } else if (tokenizer.next(TokenKind.SearchTrafo)) {
+ final SearchOption searchOption = new SearchParser().parse(tokenizer);
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return new SearchImpl().setSearchOption(searchOption);
+
+ } else if (tokenizer.next(TokenKind.QualifiedName)) {
+ return parseCustomFunction(new FullQualifiedName(tokenizer.getText()), referencedType);
+
+ } else {
+ final TokenKind kind = ParserHelper.next(tokenizer,
+ TokenKind.BottomCountTrafo, TokenKind.BottomPercentTrafo, TokenKind.BottomSumTrafo,
+ TokenKind.TopCountTrafo, TokenKind.TopPercentTrafo, TokenKind.TopSumTrafo);
+ if (kind == null) {
+ throw new UriParserSyntaxException("Invalid apply expression syntax.",
+ UriParserSyntaxException.MessageKeys.SYNTAX);
+ } else {
+ return parseBottomTop(kind, referencedType);
+ }
+ }
+ }
+
+ private Aggregate parseAggregateTrafo(EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ AggregateImpl aggregate = new AggregateImpl();
+ do {
+ aggregate.addExpression(parseAggregateExpr(referencedType));
+ } while (tokenizer.next(TokenKind.COMMA));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return aggregate;
+ }
+
+ private AggregateExpression parseAggregateExpr(EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ AggregateExpressionImpl aggregateExpression = new AggregateExpressionImpl();
+ tokenizer.saveState();
+
+ // First try is checking for a (potentially empty) path prefix and the things that could follow it.
+ UriInfoImpl uriInfo = new UriInfoImpl();
+ final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
+ if (identifierLeft != null) {
+ final String customAggregate = tokenizer.getText();
+ // A custom aggregate (an OData identifier) is defined in the CustomAggregate
+ // EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
+ // Currently we don't look into annotations, so all custom aggregates are allowed and have no type.
+ uriInfo.addResourcePart(new UriResourcePrimitivePropertyImpl(createDynamicProperty(customAggregate, null)));
+ aggregateExpression.setPath(uriInfo);
+ final String alias = parseAsAlias(referencedType, false);
+ aggregateExpression.setAlias(alias);
+ if (alias != null) {
+ ((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, null));
+ }
+ parseAggregateFrom(aggregateExpression, referencedType);
+ } else if (tokenizer.next(TokenKind.OPEN)) {
+ final UriResource lastResourcePart = uriInfo.getLastResourcePart();
+ if (lastResourcePart == null) {
+ throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
+ UriParserSyntaxException.MessageKeys.SYNTAX);
+ }
+ aggregateExpression.setPath(uriInfo);
+ DynamicStructuredType inlineType = new DynamicStructuredType((EdmStructuredType)
+ ParserHelper.getTypeInformation((UriResourcePartTyped) lastResourcePart));
+ aggregateExpression.setInlineAggregateExpression(parseAggregateExpr(inlineType));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ } else if (tokenizer.next(TokenKind.COUNT)) {
+ uriInfo.addResourcePart(new UriResourceCountImpl());
+ aggregateExpression.setPath(uriInfo);
+ final String alias = parseAsAlias(referencedType, true);
+ aggregateExpression.setAlias(alias);
+ ((DynamicStructuredType) referencedType).addProperty(
+ createDynamicProperty(alias,
+ // The OData standard mandates Edm.Decimal (with no decimals), although counts are always integer.
+ odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal)));
+ } else {
+ // No legitimate continuation of a path prefix has been found.
+
+ // Second try is checking for a common expression.
+ tokenizer.returnToSavedState();
+ final Expression expression = new ExpressionParser(edm, odata)
+ .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+ aggregateExpression.setExpression(expression);
+ parseAggregateWith(aggregateExpression);
+ if (aggregateExpression.getStandardMethod() == null && aggregateExpression.getCustomMethod() == null) {
+ throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
+ UriParserSyntaxException.MessageKeys.SYNTAX);
+ }
+ final String alias = parseAsAlias(referencedType, true);
+ aggregateExpression.setAlias(alias);
+ ((DynamicStructuredType) referencedType).addProperty(
+ createDynamicProperty(alias,
+ // Determine the type for standard methods; there is no way to do this for custom methods.
+ getTypeForAggregateMethod(aggregateExpression.getStandardMethod(),
+ ExpressionParser.getType(expression))));
+ parseAggregateFrom(aggregateExpression, referencedType);
+ }
+
+ return aggregateExpression;
+ }
+
+ private void parseAggregateWith(AggregateExpressionImpl aggregateExpression) throws UriParserException {
+ if (tokenizer.next(TokenKind.WithOperator)) {
+ final TokenKind kind = ParserHelper.next(tokenizer,
+ TokenKind.SUM, TokenKind.MIN, TokenKind.MAX, TokenKind.AVERAGE, TokenKind.COUNTDISTINCT,
+ TokenKind.QualifiedName);
+ if (kind == null) {
+ throw new UriParserSyntaxException("Invalid 'with' syntax.",
+ UriParserSyntaxException.MessageKeys.SYNTAX);
+ } else if (kind == TokenKind.QualifiedName) {
+ // A custom aggregation method is announced in the CustomAggregationMethods
+ // EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
+ // Currently we don't look into annotations, so all custom aggregation methods are allowed and have no type.
+ aggregateExpression.setCustomMethod(new FullQualifiedName(tokenizer.getText()));
+ } else {
+ aggregateExpression.setStandardMethod(TOKEN_KIND_TO_STANDARD_METHOD.get(kind));
+ }
+ }
+ }
+
+ private EdmType getTypeForAggregateMethod(final StandardMethod method, final EdmType type) {
+ if (method == StandardMethod.SUM || method == StandardMethod.AVERAGE || method == StandardMethod.COUNT_DISTINCT) {
+ return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal);
+ } else if (method == StandardMethod.MIN || method == StandardMethod.MAX) {
+ return type;
+ } else {
+ return null;
+ }
+ }
+
+ private String parseAsAlias(final EdmStructuredType referencedType, final boolean isRequired)
+ throws UriParserException {
+ if (tokenizer.next(TokenKind.AsOperator)) {
+ ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
+ final String name = tokenizer.getText();
+ if (referencedType.getProperty(name) != null) {
+ throw new UriParserSemanticException("Alias '" + name + "' is already a property.",
+ UriParserSemanticException.MessageKeys.IS_PROPERTY, name);
+ }
+ return name;
+ } else if (isRequired) {
+ throw new UriParserSyntaxException("Expected asAlias not found.", UriParserSyntaxException.MessageKeys.SYNTAX);
+ }
+ return null;
+ }
+
+ private void parseAggregateFrom(AggregateExpressionImpl aggregateExpression,
+ final EdmStructuredType referencedType) throws UriParserException {
+ while (tokenizer.next(TokenKind.FromOperator)) {
+ AggregateExpressionImpl from = new AggregateExpressionImpl();
+ from.setExpression(new MemberImpl(parseGroupingProperty(referencedType), referencedType));
+ parseAggregateWith(from);
+ aggregateExpression.addFrom(from);
+ }
+ }
+
+ private EdmProperty createDynamicProperty(final String name, final EdmType type) {
+ return name == null ? null : new DynamicProperty(name, type);
+ }
+
+ private Compute parseComputeTrafo(final EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ ComputeImpl compute = new ComputeImpl();
+ // TODO: Check when to create a new dynamic type and how it can be returned.
+ DynamicStructuredType type = new DynamicStructuredType(referencedType);
+ do {
+ final Expression expression = new ExpressionParser(edm, odata)
+ .parse(tokenizer, type, crossjoinEntitySetNames, aliases);
+ final EdmType expressionType = ExpressionParser.getType(expression);
+ if (expressionType.getKind() != EdmTypeKind.PRIMITIVE) {
+ throw new UriParserSemanticException("Compute expressions must return primitive values.",
+ UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES, "compute");
+ }
+ final String alias = parseAsAlias(type, true);
+ type.addProperty(createDynamicProperty(alias, expressionType));
+ compute.addExpression(new ComputeExpressionImpl()
+ .setExpression(expression)
+ .setAlias(alias));
+ } while (tokenizer.next(TokenKind.COMMA));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return compute;
+ }
+
+ private Concat parseConcatTrafo(final EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ ConcatImpl concat = new ConcatImpl();
+ // TODO: Check when to create a new dynamic type and how it can be returned.
+ concat.addApplyOption(parseApply(referencedType));
+ ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+ do {
+ concat.addApplyOption(parseApply(referencedType));
+ } while (tokenizer.next(TokenKind.COMMA));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return concat;
+ }
+
+ private ExpandOption parseExpandTrafo(final EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ ExpandItemImpl item = new ExpandItemImpl();
+ item.setResourcePath(ExpandParser.parseExpandPath(tokenizer, edm, referencedType, item));
+ final EdmType type = ParserHelper.getTypeInformation((UriResourcePartTyped)
+ ((UriInfoImpl) item.getResourcePath()).getLastResourcePart());
+ if (tokenizer.next(TokenKind.COMMA)) {
+ if (tokenizer.next(TokenKind.FilterTrafo)) {
+ item.setSystemQueryOption(
+ new FilterParser(edm, odata).parse(tokenizer,type, crossjoinEntitySetNames, aliases));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ } else {
+ ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
+ item.setSystemQueryOption(parseExpandTrafo((EdmStructuredType) type));
+ }
+ }
+ while (tokenizer.next(TokenKind.COMMA)) {
+ ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
+ final ExpandOption nestedExpand = parseExpandTrafo((EdmStructuredType) type);
+ if (item.getExpandOption() == null) {
+ item.setSystemQueryOption(nestedExpand);
+ } else {
+ // Add to the existing items.
+ ((ExpandOptionImpl) item.getExpandOption())
+ .addExpandItem(nestedExpand.getExpandItems().get(0));
+ }
+ }
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ ExpandOptionImpl expand = new ExpandOptionImpl();
+ expand.addExpandItem(item);
+ return expand;
+ }
+
+ private GroupBy parseGroupByTrafo(final EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ GroupByImpl groupBy = new GroupByImpl();
+ parseGroupByList(groupBy, referencedType);
+ if (tokenizer.next(TokenKind.COMMA)) {
+ groupBy.setApplyOption(parseApply(referencedType));
+ }
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return groupBy;
+ }
+
+ private void parseGroupByList(GroupByImpl groupBy, final EdmStructuredType referencedType)
+ throws UriParserException {
+ ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
+ do {
+ groupBy.addGroupByItem(parseGroupByElement(referencedType));
+ } while (tokenizer.next(TokenKind.COMMA));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ }
+
+ private GroupByItem parseGroupByElement(final EdmStructuredType referencedType)
+ throws UriParserException {
+ if (tokenizer.next(TokenKind.RollUpSpec)) {
+ return parseRollUpSpec(referencedType);
+ } else {
+ return new GroupByItemImpl().setPath(parseGroupingProperty(referencedType));
+ }
+ }
+
+ private GroupByItem parseRollUpSpec(final EdmStructuredType referencedType)
+ throws UriParserException {
+ GroupByItemImpl item = new GroupByItemImpl();
+ if (tokenizer.next(TokenKind.ROLLUP_ALL)) {
+ item.setIsRollupAll();
+ } else {
+ item.addRollupItem(new GroupByItemImpl().setPath(
+ parseGroupingProperty(referencedType)));
+ }
+ ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+ do {
+ item.addRollupItem(new GroupByItemImpl().setPath(
+ parseGroupingProperty(referencedType)));
+ } while (tokenizer.next(TokenKind.COMMA));
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return item;
+ }
+
+ private UriInfo parseGroupingProperty(final EdmStructuredType referencedType) throws UriParserException {
+ UriInfoImpl uriInfo = new UriInfoImpl();
+ final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
+ if (identifierLeft != null) {
+ throw new UriParserSemanticException("Unknown identifier in grouping property path.",
+ UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE,
+ identifierLeft,
+ uriInfo.getLastResourcePart() != null && uriInfo.getLastResourcePart() instanceof UriResourcePartTyped ?
+ ((UriResourcePartTyped) uriInfo.getLastResourcePart())
+ .getType().getFullQualifiedName().getFullQualifiedNameAsString() :
+ "");
+ }
+ if (uriInfo.getLastResourcePart() != null
+ && uriInfo.getLastResourcePart().getKind() == UriResourceKind.navigationProperty) {
+ if (tokenizer.next(TokenKind.SLASH)) {
+ UriResourceNavigationPropertyImpl lastPart = (UriResourceNavigationPropertyImpl) uriInfo.getLastResourcePart();
+ final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm,
+ (EdmStructuredType) lastPart.getType());
+ lastPart.setCollectionTypeFilter(typeCast);
+ }
+ }
+ return uriInfo;
+ }
+
+ /**
+ * Parses the path prefix and a following OData identifier as one path, deviating from the ABNF.
+ * @param uriInfo object to be filled with path segments
+ * @return a parsed but not used OData identifier */
+ private String parsePathPrefix(UriInfoImpl uriInfo, final EdmStructuredType referencedType)
+ throws UriParserException {
+ final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm, referencedType);
+ if (typeCast != null) {
+ ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
+ }
+ EdmStructuredType type = typeCast == null ? referencedType : typeCast;
+ while (tokenizer.next(TokenKind.ODataIdentifier)) {
+ final String name = tokenizer.getText();
+ final EdmElement property = type.getProperty(name);
+ final UriResource segment = parsePathSegment(property);
+ if (segment == null) {
+ if (property == null) {
+ return name;
+ } else {
+ uriInfo.addResourcePart(
+ property instanceof EdmNavigationProperty ?
+ new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property) :
+ property.getType().getKind() == EdmTypeKind.COMPLEX ?
+ new UriResourceComplexPropertyImpl((EdmProperty) property) :
+ new UriResourcePrimitivePropertyImpl((EdmProperty) property));
+ return null;
+ }
+ } else {
+ uriInfo.addResourcePart(segment);
+ }
+ type = (EdmStructuredType) ParserHelper.getTypeInformation((UriResourcePartTyped) segment);
+ }
+ return null;
+ }
+
+ private UriResource parsePathSegment(final EdmElement property) throws UriParserException {
+ if (property == null
+ || !(property.getType().getKind() == EdmTypeKind.COMPLEX
+ || property instanceof EdmNavigationProperty)) {
+ // Could be a customAggregate or $count.
+ return null;
+ }
+ if (tokenizer.next(TokenKind.SLASH)) {
+ final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm,
+ (EdmStructuredType) property.getType());
+ if (typeCast != null) {
+ ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
+ }
+ return property.getType().getKind() == EdmTypeKind.COMPLEX ?
+ new UriResourceComplexPropertyImpl((EdmProperty) property).setTypeFilter(typeCast) :
+ new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property).setCollectionTypeFilter(typeCast);
+ } else {
+ return null;
+ }
+ }
+
+ private CustomFunction parseCustomFunction(final FullQualifiedName functionName,
+ final EdmStructuredType referencedType) throws UriParserException, UriValidationException {
+ final List<UriParameter> parameters =
+ ParserHelper.parseFunctionParameters(tokenizer, edm, referencedType, true, aliases);
+ final List<String> parameterNames = ParserHelper.getParameterNames(parameters);
+ final EdmFunction function = edm.getBoundFunction(functionName,
+ referencedType.getFullQualifiedName(), true, parameterNames);
+ if (function == null) {
+ throw new UriParserSemanticException("No function '" + functionName + "' found.",
+ UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND,
+ functionName.getFullQualifiedNameAsString());
+ }
+ ParserHelper.validateFunctionParameters(function, parameters, edm, referencedType, aliases);
+
+ // The binding parameter and the return type must be of type complex or entity collection.
+ final EdmParameter bindingParameter = function.getParameter(function.getParameterNames().get(0));
+ final EdmReturnType returnType = function.getReturnType();
+ if (bindingParameter.getType().getKind() != EdmTypeKind.ENTITY
+ && bindingParameter.getType().getKind() != EdmTypeKind.COMPLEX
+ || !bindingParameter.isCollection()
+ || returnType.getType().getKind() != EdmTypeKind.ENTITY
+ && returnType.getType().getKind() != EdmTypeKind.COMPLEX
+ || !returnType.isCollection()) {
+ throw new UriParserSemanticException("Only entity- or complex-collection functions are allowed.",
+ UriParserSemanticException.MessageKeys.FUNCTION_MUST_USE_COLLECTIONS,
+ functionName.getFullQualifiedNameAsString());
+ }
+
+ return new CustomFunctionImpl().setFunction(function).setParameters(parameters);
+ }
+
+ private BottomTop parseBottomTop(final TokenKind kind, final EdmStructuredType referencedType)
+ throws UriParserException, UriValidationException {
+ BottomTopImpl bottomTop = new BottomTopImpl();
+ bottomTop.setMethod(TOKEN_KIND_TO_BOTTOM_TOP_METHOD.get(kind));
+ final ExpressionParser expressionParser = new ExpressionParser(edm, odata);
+ final Expression number = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+ expressionParser.checkIntegerType(number);
+ bottomTop.setNumber(number);
+ ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
+ final Expression value = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
+ expressionParser.checkNumericType(value);
+ bottomTop.setValue(value);
+ ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
+ return bottomTop;
+ }
+}