[OLINGO-935]URI-parser support of the data aggregation extension Signed-off-by: Christian Amend <[email protected]>
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 <[email protected]> Authored: Fri Apr 15 16:11:30 2016 +0200 Committer: Christian Amend <[email protected]> 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; + } +}
