[OLINGO-935]URI-parser support of the data aggregation extension

Signed-off-by: Christian Amend <christian.amend@sap.com>
This commit is contained in:
Klaus Straubinger 2016-04-15 16:11:30 +02:00 committed by Christian Amend
parent 824c174d75
commit 6553e95080
46 changed files with 3416 additions and 108 deletions

View File

@ -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;
@ -99,6 +100,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.

View File

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

View File

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

View File

@ -82,7 +82,12 @@ public enum SystemQueryOptionKind {
/**
* @see LevelsExpandOption
*/
LEVELS("$levels");
LEVELS("$levels"),
/**
* @see ApplyOption
*/
APPLY("$apply");
private final String syntax;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
}

View File

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

View File

@ -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

View File

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

View File

@ -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:
@ -258,6 +255,11 @@ public class UriInfoImpl implements UriInfo {
return (TopOption) systemQueryOptions.get(SystemQueryOptionKind.TOP);
}
@Override
public ApplyOption getApplyOption() {
return (ApplyOption) systemQueryOptions.get(SystemQueryOptionKind.APPLY);
}
@Override
public List<SystemQueryOption> getSystemQueryOptions() {
return Collections.unmodifiableList(new ArrayList<SystemQueryOption>(systemQueryOptions.values()));

View File

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

View File

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

View File

@ -67,9 +67,15 @@ public class UriParserSemanticException extends UriParserException {
COMPLEX_PARAMETER_IN_RESOURCE_PATH,
/** parameters: left type, right type */
TYPES_NOT_COMPATIBLE,
/** parameter: addressed resource name*/
NOT_A_MEDIA_RESOURCE;
/** parameter: addressed resource name */
NOT_A_MEDIA_RESOURCE,
/** parameters: property name */
IS_PROPERTY,
/** parameter: expression */
ONLY_FOR_PRIMITIVE_TYPES,
/** parameter: function name */
FUNCTION_MUST_USE_COLLECTIONS;
@Override
public String getKey() {
return name();

View File

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

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;
public class ApplyOptionImpl extends SystemQueryOptionImpl implements ApplyOption {
private List<ApplyItem> transformations = new ArrayList<ApplyItem>();
public ApplyOptionImpl() {
setKind(SystemQueryOptionKind.APPLY);
}
@Override
public List<ApplyItem> getApplyItems() {
return Collections.unmodifiableList(transformations);
}
public ApplyOptionImpl add(final ApplyItem transformation) {
transformations.add(transformation);
return this;
}
}

View File

@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
/**
* Represents an aggregate expression.
*/
public class AggregateExpressionImpl implements AggregateExpression {
private UriInfo path;
private Expression expression;
private StandardMethod standardMethod;
private FullQualifiedName customMethod;
private String alias;
private AggregateExpression inlineAggregateExpression;
private List<AggregateExpression> from = new ArrayList<AggregateExpression>();
@Override
public List<UriResource> getPath() {
return path == null ? Collections.<UriResource> emptyList() : path.getUriResourceParts();
}
public AggregateExpressionImpl setPath(final UriInfo uriInfo) {
path = uriInfo;
return this;
}
@Override
public Expression getExpression() {
return expression;
}
public AggregateExpressionImpl setExpression(final Expression expression) {
this.expression = expression;
return this;
}
@Override
public StandardMethod getStandardMethod() {
return standardMethod;
}
public AggregateExpressionImpl setStandardMethod(final StandardMethod standardMethod) {
this.standardMethod = standardMethod;
return this;
}
@Override
public FullQualifiedName getCustomMethod() {
return customMethod;
}
public AggregateExpressionImpl setCustomMethod(final FullQualifiedName customMethod) {
this.customMethod = customMethod;
return this;
}
@Override
public AggregateExpression getInlineAggregateExpression() {
return inlineAggregateExpression;
}
public AggregateExpressionImpl setInlineAggregateExpression(final AggregateExpression aggregateExpression) {
inlineAggregateExpression = aggregateExpression;
return this;
}
@Override
public List<AggregateExpression> getFrom() {
return Collections.unmodifiableList(from);
}
public AggregateExpressionImpl addFrom(final AggregateExpression from) {
this.from.add(from);
return this;
}
@Override
public String getAlias() {
return alias;
}
public AggregateExpressionImpl setAlias(final String alias) {
this.alias = alias;
return this;
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
/**
* Represents the aggregate transformation.
*/
public class AggregateImpl implements Aggregate {
private List<AggregateExpression> expressions = new ArrayList<AggregateExpression>();
@Override
public Kind getKind() {
return Kind.AGGREGATE;
}
@Override
public List<AggregateExpression> getExpressions() {
return expressions;
}
public AggregateImpl addExpression(final AggregateExpression expression) {
expressions.add(expression);
return this;
}
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
/**
* Represents a transformation with one of the pre-defined methods
* <code>bottomcount</code>, <code>bottompercent</code>, <code>bottomsum</code>,
* <code>topcount</code>, <code>toppercent</code>, <code>topsum</code>.
*/
public class BottomTopImpl implements BottomTop {
private Method method;
private Expression number;
private Expression value;
@Override
public Kind getKind() {
return Kind.BOTTOM_TOP;
}
@Override
public Method getMethod() {
return method;
}
public BottomTopImpl setMethod(final Method method) {
this.method = method;
return this;
}
@Override
public Expression getNumber() {
return number;
}
public BottomTopImpl setNumber(final Expression number) {
this.number = number;
return this;
}
@Override
public Expression getValue() {
return value;
}
public BottomTopImpl setValue(final Expression value) {
this.value = value;
return this;
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
/**
* Represents an aggregate expression.
*/
public class ComputeExpressionImpl implements ComputeExpression {
private Expression expression;
private String alias;
@Override
public Expression getExpression() {
return expression;
}
public ComputeExpressionImpl setExpression(final Expression expression) {
this.expression = expression;
return this;
}
@Override
public String getAlias() {
return alias;
}
public ComputeExpressionImpl setAlias(final String alias) {
this.alias = alias;
return this;
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
/**
* Represents the compute transformation.
*/
public class ComputeImpl implements Compute {
private List<ComputeExpression> expressions = new ArrayList<ComputeExpression>();
@Override
public Kind getKind() {
return Kind.COMPUTE;
}
@Override
public List<ComputeExpression> getExpressions() {
return expressions;
}
public ComputeImpl addExpression(final ComputeExpressionImpl expression) {
expressions.add(expression);
return this;
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
/**
* Represents the concat transformation.
*/
public class ConcatImpl implements Concat {
private List<ApplyOption> options = new ArrayList<ApplyOption>();
@Override
public Kind getKind() {
return Kind.CONCAT;
}
@Override
public List<ApplyOption> getApplyOptions() {
return options;
}
public ConcatImpl addApplyOption(final ApplyOption option) {
options.add(option);
return this;
}
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.Collections;
import java.util.List;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.server.api.uri.UriParameter;
import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
/**
* Represents a transformation with a custom function.
*/
public class CustomFunctionImpl implements CustomFunction {
private EdmFunction function = null;
private List<UriParameter> parameters;
@Override
public Kind getKind() {
return Kind.CUSTOM_FUNCTION;
}
@Override
public EdmFunction getFunction() {
return function;
}
public CustomFunctionImpl setFunction(final EdmFunction function) {
this.function = function;
return this;
}
@Override
public List<UriParameter> getParameters() {
return parameters == null ?
Collections.<UriParameter> emptyList() :
Collections.unmodifiableList(parameters);
}
public CustomFunctionImpl setParameters(final List<UriParameter> parameters) {
this.parameters = parameters;
return this;
}
}

View File

@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.Collections;
import java.util.List;
import org.apache.olingo.commons.api.edm.EdmAnnotation;
import org.apache.olingo.commons.api.edm.EdmMapping;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmTerm;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.commons.api.edm.geo.SRID;
/** A dynamic EDM property containing an aggregation. */
public class DynamicProperty implements EdmProperty {
private final String name;
private final EdmType propertyType;
/** Creates a dynamic property with a mandatory name and an optional type. */
public DynamicProperty(final String name, final EdmType type) {
this.name = name;
propertyType = type;
}
@Override
public String getName() {
return name;
}
@Override
public EdmType getType() {
return propertyType;
}
@Override
public boolean isCollection() {
return false;
}
@Override
public EdmMapping getMapping() {
return null;
}
@Override
public String getMimeType() {
return null;
}
@Override
public boolean isNullable() {
return false;
}
@Override
public Integer getMaxLength() {
return null;
}
@Override
public Integer getPrecision() {
return null;
}
@Override
public Integer getScale() {
return null;
}
@Override
public SRID getSrid() {
return null;
}
@Override
public boolean isUnicode() {
return true;
}
@Override
public String getDefaultValue() {
return null;
}
@Override
public boolean isPrimitive() {
return propertyType != null && propertyType.getKind() == EdmTypeKind.PRIMITIVE;
}
@Override
public EdmAnnotation getAnnotation(final EdmTerm term, final String qualifier) {
return null;
}
@Override
public List<EdmAnnotation> getAnnotations() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,141 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.edm.EdmAnnotation;
import org.apache.olingo.commons.api.edm.EdmElement;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.commons.api.edm.EdmTerm;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
/** A dynamic structured type used to incorporate dynamic properties containing aggregations. */
public class DynamicStructuredType implements EdmStructuredType, Cloneable {
private final EdmStructuredType startType;
private Map<String, EdmProperty> properties;
public DynamicStructuredType(final EdmStructuredType startType) {
this.startType = startType;
}
public DynamicStructuredType addProperty(final EdmProperty property) {
if (properties == null) {
properties = new LinkedHashMap<String, EdmProperty>();
}
properties.put(property.getName(), property);
return this;
}
@Override
public EdmElement getProperty(final String name) {
final EdmElement property = startType.getProperty(name);
return property == null ?
properties == null ? null : properties.get(name) :
property;
}
@Override
public List<String> getPropertyNames() {
if (properties == null || properties.isEmpty()) {
return startType.getPropertyNames();
} else {
List<String> names = new ArrayList<String>(startType.getPropertyNames());
names.addAll(properties.keySet());
return Collections.unmodifiableList(names);
}
}
@Override
public EdmProperty getStructuralProperty(final String name) {
final EdmProperty property = startType.getStructuralProperty(name);
return property == null ?
properties == null ? null : properties.get(name) :
property;
}
@Override
public EdmNavigationProperty getNavigationProperty(final String name) {
return startType.getNavigationProperty(name);
}
@Override
public List<String> getNavigationPropertyNames() {
return startType.getNavigationPropertyNames();
}
@Override
public String getNamespace() {
return startType.getNamespace();
}
@Override
public String getName() {
return startType.getName();
}
@Override
public FullQualifiedName getFullQualifiedName() {
return startType.getFullQualifiedName();
}
@Override
public EdmTypeKind getKind() {
return startType.getKind();
}
@Override
public EdmAnnotation getAnnotation(final EdmTerm term, final String qualifier) {
return startType.getAnnotation(term, qualifier);
}
@Override
public List<EdmAnnotation> getAnnotations() {
return startType.getAnnotations();
}
@Override
public EdmStructuredType getBaseType() {
return startType.getBaseType();
}
@Override
public boolean compatibleTo(final EdmType targetType) {
return startType.compatibleTo(targetType);
}
@Override
public boolean isOpenType() {
return startType.isOpenType();
}
@Override
public boolean isAbstract() {
return startType.isAbstract();
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
/**
* Represents the expand transformation.
*/
public class ExpandImpl implements Expand {
private ExpandOption expandOption = null;
@Override
public Kind getKind() {
return Kind.EXPAND;
}
@Override
public ExpandOption getExpandOption() {
return expandOption;
}
public ExpandImpl setExpandOption(final ExpandOption expandOption) {
this.expandOption = expandOption;
return this;
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.FilterOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
/**
* Represents the filter transformation.
*/
public class FilterImpl implements Filter {
private FilterOption filterOption = null;
@Override
public Kind getKind() {
return Kind.FILTER;
}
@Override
public FilterOption getFilterOption() {
return filterOption;
}
public FilterImpl setFilterOption(final FilterOption filterOption) {
this.filterOption = filterOption;
return this;
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
/**
* Represents the grouping transformation.
*/
public class GroupByImpl implements GroupBy {
private ApplyOption applyOption;
private List<GroupByItem> groupByItems = new ArrayList<GroupByItem>();
@Override
public Kind getKind() {
return Kind.GROUP_BY;
}
@Override
public ApplyOption getApplyOption() {
return applyOption;
}
public GroupByImpl setApplyOption(final ApplyOption applyOption) {
this.applyOption = applyOption;
return this;
}
@Override
public List<GroupByItem> getGroupByItems() {
return groupByItems;
}
public GroupByImpl addGroupByItem(final GroupByItem groupByItem) {
groupByItems.add(groupByItem);
return this;
}
}

View File

@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
/**
* Represents a grouping property.
*/
public class GroupByItemImpl implements GroupByItem {
private UriInfo path;
private boolean isRollupAll;
private List<GroupByItem> rollup = new ArrayList<GroupByItem>();
@Override
public List<UriResource> getPath() {
return path == null ? Collections.<UriResource> emptyList() : path.getUriResourceParts();
}
public GroupByItemImpl setPath(final UriInfo uriInfo) {
path = uriInfo;
return this;
}
@Override
public List<GroupByItem> getRollup() {
return rollup;
}
public GroupByItemImpl addRollupItem(final GroupByItem groupByItem) {
rollup.add(groupByItem);
return this;
}
@Override
public boolean isRollupAll() {
return isRollupAll;
}
public GroupByItemImpl setIsRollupAll() {
this.isRollupAll = true;
return this;
}
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.apply.Identity;
/**
* Represents the identity transformation.
*/
public class IdentityImpl implements Identity {
@Override
public Kind getKind() {
return Kind.IDENTITY;
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.queryoption.apply;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Search;
/**
* Represents the search transformation.
*/
public class SearchImpl implements Search {
private SearchOption searchOption = null;
@Override
public Kind getKind() {
return Kind.SEARCH;
}
@Override
public SearchOption getSearchOption() {
return searchOption;
}
public SearchImpl setSearchOption(final SearchOption searchOption) {
this.searchOption = searchOption;
return this;
}
}

View File

@ -45,27 +45,27 @@ public class UriValidator {
//CHECKSTYLE:OFF (Maven checkstyle)
private static final boolean[][] decisionMatrix =
{
/* 0-FILTER 1-FORMAT 2-EXPAND 3-ID 4-COUNT 5-ORDERBY 6-SEARCH 7-SELECT 8-SKIP 9-SKIPTOKEN 10-TOP */
/* all 0 */ { true , true , true , false, true , true , true , true , true , true , true },
/* batch 1 */ { false, false, false, false, false, false, false, false, false, false, false },
/* crossjoin 2 */ { true , true , true , false, true , true , true , true , true , true , true },
/* entityId 3 */ { false, true , true , true , false, false, false, true , false, false, false },
/* metadata 4 */ { false, true , false, false, false, false, false, false, false, false, false },
/* service 5 */ { false, true , false, false, false, false, false, false, false, false, false },
/* entitySet 6 */ { true , true , true , false, true , true , true , true , true , true , true },
/* entitySetCount 7 */ { true , false, false, false, false, false, true , false, false, false, false },
/* entity 8 */ { false, true , true , false, false, false, false, true , false, false, false },
/* mediaStream 9 */ { false, false, false, false, false, false, false, false, false, false, false },
/* references 10 */ { true , true , false, false, true , true , true , false, true , true , true },
/* reference 11 */ { false, true , false, false, false, false, false, false, false, false, false },
/* propertyComplex 12 */ { false, true , true , false, false, false, false, true , false, false, false },
/* propertyComplexCollection 13 */ { true , true , true , false, true , true , false, true , true , true , true },
/* propertyComplexCollectionCount 14 */ { true , false, false, false, false, false, false, false, false, false, false },
/* propertyPrimitive 15 */ { false, true , false, false, false, false, false, false, false, false, false },
/* propertyPrimitiveCollection 16 */ { true , true , false, false, true , true , false, false, true , true , true },
/* propertyPrimitiveCollectionCount 17 */ { true , false, false, false, false, false, false, false, false, false, false },
/* propertyPrimitiveValue 18 */ { false, true , false, false, false, false, false, false, false, false, false },
/* none 19 */ { false, true , false, false, false, false, false, false, false, false, false }
/* 0-FILTER 1-FORMAT 2-EXPAND 3-ID 4-COUNT 5-ORDERBY 6-SEARCH 7-SELECT 8-SKIP 9-SKIPTOKEN 10-TOP 11-APPLY */
/* all 0 */ { true , true , true , false, true , true , true , true , true , true , true , true },
/* batch 1 */ { false, false, false, false, false, false, false, false, false, false, false, false },
/* crossjoin 2 */ { true , true , true , false, true , true , true , true , true , true , true , true },
/* entityId 3 */ { false, true , true , true , false, false, false, true , false, false, false, false },
/* metadata 4 */ { false, true , false, false, false, false, false, false, false, false, false, false },
/* service 5 */ { false, true , false, false, false, false, false, false, false, false, false, false },
/* entitySet 6 */ { true , true , true , false, true , true , true , true , true , true , true , true },
/* entitySetCount 7 */ { true , false, false, false, false, false, true , false, false, false, false, false },
/* entity 8 */ { false, true , true , false, false, false, false, true , false, false, false, false },
/* mediaStream 9 */ { false, false, false, false, false, false, false, false, false, false, false, false },
/* references 10 */ { true , true , false, false, true , true , true , false, true , true , true , false },
/* reference 11 */ { false, true , false, false, false, false, false, false, false, false, false, false },
/* propertyComplex 12 */ { false, true , true , false, false, false, false, true , false, false, false, false },
/* propertyComplexCollection 13 */ { true , true , true , false, true , true , false, true , true , true , true , true },
/* propertyComplexCollectionCount 14 */ { true , false, false, false, false, false, false, false, false, false, false, false },
/* propertyPrimitive 15 */ { false, true , false, false, false, false, false, false, false, false, false, false },
/* propertyPrimitiveCollection 16 */ { true , true , false, false, true , true , false, false, true , true , true , false },
/* propertyPrimitiveCollectionCount 17 */ { true , false, false, false, false, false, false, false, false, false, false, false },
/* propertyPrimitiveValue 18 */ { false, true , false, false, false, false, false, false, false, false, false, false },
/* none 19 */ { false, true , false, false, false, false, false, false, false, false, false, false }
};
//CHECKSTYLE:ON
//@formatter:on
@ -118,6 +118,7 @@ public class UriValidator {
temp.put(SystemQueryOptionKind.SKIP, 8);
temp.put(SystemQueryOptionKind.SKIPTOKEN, 9);
temp.put(SystemQueryOptionKind.TOP, 10);
temp.put(SystemQueryOptionKind.APPLY, 11);
OPTION_INDEX = Collections.unmodifiableMap(temp);
}
@ -255,7 +256,6 @@ public class UriValidator {
private UriType getUriTypeForFunction(final UriResource lastPathSegment) throws UriValidationException {
final UriResourceFunction uriFunction = (UriResourceFunction) lastPathSegment;
final boolean isCollection = uriFunction.isCollection();
final EdmTypeKind typeKind = uriFunction.getFunction().getReturnType().getType().getKind();
UriType uriType;

View File

@ -73,6 +73,9 @@ UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is n
UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments; found: '%1$s'.
UriParserSemanticException.TYPES_NOT_COMPATIBLE=The types '%1$s' and '%2$s' are not compatible.
UriParserSemanticException.NOT_A_MEDIA_RESOURCE=The resource '%1$s' is not a media resource. $value can only be applied on media resources.
UriParserSemanticException.IS_PROPERTY=The identifier '%1$s' is already used as a property.
UriParserSemanticException.ONLY_FOR_PRIMITIVE_TYPES='%1$s' is only allowed for primitive-type expressions.
UriParserSemanticException.FUNCTION_MUST_USE_COLLECTIONS=Only bound functions with collections of structural types as binding parameter and as return type are allowed; '%1$s' is not such a function.
UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported.
UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported.

View File

@ -96,6 +96,18 @@ public class UriTokenizerTest {
assertTrue(tokenizer.next(TokenKind.EOF));
}
@Test
public void saveState() {
UriTokenizer tokenizer = new UriTokenizer("a*");
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
tokenizer.saveState();
assertTrue(tokenizer.next(TokenKind.STAR));
assertTrue(tokenizer.next(TokenKind.EOF));
tokenizer.returnToSavedState();
assertTrue(tokenizer.next(TokenKind.STAR));
assertTrue(tokenizer.next(TokenKind.EOF));
}
@Test
public void systemQueryOptions() {
UriTokenizer tokenizer = new UriTokenizer("$expand=*;$filter=true;$levels=max;$orderby=false");
@ -642,6 +654,64 @@ public class UriTokenizerTest {
wrongToken(TokenKind.GeometryCollection, "geometry'SRID=0;Collection(Point(1 2),Point(3 4))'", 'x');
}
@Test
public void aggregation() {
UriTokenizer tokenizer = new UriTokenizer("$apply=aggregate(a with sum as s from x with average)");
assertTrue(tokenizer.next(TokenKind.APPLY));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.AggregateTrafo));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.WithOperator));
assertTrue(tokenizer.next(TokenKind.SUM));
assertTrue(tokenizer.next(TokenKind.AsOperator));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.FromOperator));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.WithOperator));
assertTrue(tokenizer.next(TokenKind.AVERAGE));
assertTrue(tokenizer.next(TokenKind.CLOSE));
assertTrue(tokenizer.next(TokenKind.EOF));
tokenizer = new UriTokenizer("a with min as m");
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.WithOperator));
assertTrue(tokenizer.next(TokenKind.MIN));
tokenizer = new UriTokenizer("a with countdistinct as c");
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.WithOperator));
assertTrue(tokenizer.next(TokenKind.COUNTDISTINCT));
assertTrue(new UriTokenizer("identity").next(TokenKind.IDENTITY));
assertTrue(new UriTokenizer("bottomcount(1,x)").next(TokenKind.BottomCountTrafo));
assertTrue(new UriTokenizer("bottompercent(1,x)").next(TokenKind.BottomPercentTrafo));
assertTrue(new UriTokenizer("bottomsum(1,x)").next(TokenKind.BottomSumTrafo));
assertTrue(new UriTokenizer("topcount(1,x)").next(TokenKind.TopCountTrafo));
assertTrue(new UriTokenizer("toppercent(1,x)").next(TokenKind.TopPercentTrafo));
assertTrue(new UriTokenizer("topsum(1,x)").next(TokenKind.TopSumTrafo));
assertTrue(new UriTokenizer("compute(a mul b as m)").next(TokenKind.ComputeTrafo));
assertTrue(new UriTokenizer("search(a)").next(TokenKind.SearchTrafo));
assertTrue(new UriTokenizer("expand(a)").next(TokenKind.ExpandTrafo));
assertTrue(new UriTokenizer("filter(true)").next(TokenKind.FilterTrafo));
tokenizer = new UriTokenizer("groupby((rollup($all,x,y)))");
assertTrue(tokenizer.next(TokenKind.GroupByTrafo));
assertTrue(tokenizer.next(TokenKind.OPEN));
assertTrue(tokenizer.next(TokenKind.RollUpSpec));
assertTrue(tokenizer.next(TokenKind.ROLLUP_ALL));
assertTrue(tokenizer.next(TokenKind.COMMA));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.COMMA));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.CLOSE));
assertTrue(tokenizer.next(TokenKind.CLOSE));
assertTrue(tokenizer.next(TokenKind.CLOSE));
assertTrue(tokenizer.next(TokenKind.EOF));
assertTrue(new UriTokenizer("isdefined(x)").next(TokenKind.IsDefinedMethod));
}
private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) {
assertFalse(new UriTokenizer(disturbCharacter + value).next(kind));

View File

@ -233,7 +233,7 @@ public abstract class TechnicalProcessor implements Processor {
}
protected void validateOptions(final UriInfoResource uriInfo) throws ODataApplicationException {
if (uriInfo.getIdOption() != null) {
if (uriInfo.getIdOption() != null || uriInfo.getApplyOption() != null) {
throw new ODataApplicationException("Not all of the specified options are supported.",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
}

View File

@ -0,0 +1,676 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.parser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edmx.EdmxReference;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop.Method;
import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
import org.apache.olingo.server.api.uri.queryoption.apply.ComputeExpression;
import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
import org.apache.olingo.server.api.uri.queryoption.apply.Expand;
import org.apache.olingo.server.api.uri.queryoption.apply.Filter;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
import org.apache.olingo.server.api.uri.queryoption.apply.Identity;
import org.apache.olingo.server.api.uri.queryoption.apply.Search;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.testutil.ExpandValidator;
import org.apache.olingo.server.core.uri.testutil.FilterValidator;
import org.apache.olingo.server.core.uri.testutil.ResourceValidator;
import org.apache.olingo.server.core.uri.testutil.TestUriValidator;
import org.apache.olingo.server.core.uri.testutil.TestValidator;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
import org.apache.olingo.server.tecsvc.provider.ComplexTypeProvider;
import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
import org.apache.olingo.server.tecsvc.provider.EntityTypeProvider;
import org.apache.olingo.server.tecsvc.provider.FunctionProvider;
import org.apache.olingo.server.tecsvc.provider.PropertyProvider;
import org.junit.Test;
/** Tests of the $apply parser inspired by the ABNF test cases. */
public class ApplyParserTest {
private static final OData odata = OData.newInstance();
private static final Edm edm = odata.createServiceMetadata(
new EdmTechProvider(), Collections.<EdmxReference> emptyList()).getEdm();
@Test
public void aggregate() throws Exception {
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with sum as s)")
.is(Aggregate.class)
.goAggregate(0).isStandardMethod(StandardMethod.SUM).isAlias("s")
.goExpression().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with min as m)")
.goAggregate(0).isStandardMethod(StandardMethod.MIN).isAlias("m");
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with max as m)")
.goAggregate(0).isStandardMethod(StandardMethod.MAX).isAlias("m");
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with average as a)")
.goAggregate(0).isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with countdistinct as c)")
.goAggregate(0).isStandardMethod(StandardMethod.COUNT_DISTINCT).isAlias("c");
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with custom.aggregate as c)")
.is(Aggregate.class)
.goAggregate(0).isCustomMethod(new FullQualifiedName("custom", "aggregate")).isAlias("c");
parseEx("ESTwoKeyNav", "aggregate()")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with sum)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 as s)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with SUM as s)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyString with countdistinct as PropertyInt16)")
.isExSemantic(UriParserSemanticException.MessageKeys.IS_PROPERTY);
}
@Test
public void aggregateExpression() throws Exception {
parse("ESTwoKeyNav", "aggregate(PropertyInt16 mul PropertyComp/PropertyInt16 with sum as s)")
.is(Aggregate.class)
.goAggregate(0).isStandardMethod(StandardMethod.SUM)
.goExpression().isBinary(BinaryOperatorKind.MUL)
.left().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false)
.goUpFilterValidator().root()
.right().goPath().first().isComplexProperty("PropertyComp", ComplexTypeProvider.nameCTPrimComp, false)
.n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav",
"aggregate(NavPropertyETKeyNavMany(PropertyInt16 mul NavPropertyETTwoKeyNavOne/PropertyInt16 with sum as s))")
.goAggregate(0)
.goInlineAggregateExpression().isStandardMethod(StandardMethod.SUM)
.goUpAggregate()
.goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
parseEx("ESTwoKeyNav", "aggregate((PropertyInt16 mul 2 with sum as s))")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
}
@Test
public void aggregateCount() throws Exception {
parse("ESTwoKeyNav", "aggregate($count as count)")
.is(Aggregate.class)
.goAggregate(0).goPath().first().isCount();
parseEx("ESTwoKeyNav", "aggregate($count)").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate($count with sum as count)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
}
@Test
public void aggregateFrom() throws Exception {
parse("ESTwoKeyNav", "aggregate(PropertyInt16 with sum as s from CollPropertyComp with average)")
.goAggregate(0).isStandardMethod(StandardMethod.SUM)
.goFrom(0).isStandardMethod(StandardMethod.AVERAGE)
.goExpression().goPath().first()
.isComplexProperty("CollPropertyComp", ComplexTypeProvider.nameCTPrimComp, true);
parse("ESTwoKeyNav",
"aggregate(PropertyInt16 with sum as s from CollPropertyComp with average from CollPropertyString with max)")
.goAggregate(0).isStandardMethod(StandardMethod.SUM)
.goFrom(0).isStandardMethod(StandardMethod.AVERAGE)
.goUpAggregate().goFrom(1).isStandardMethod(StandardMethod.MAX);
parse("ESTwoKeyNav", "aggregate(customAggregate as a from CollPropertyComp with average)")
.goAggregate(0).goFrom(0).isStandardMethod(StandardMethod.AVERAGE);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 as a from CollPropertyComp with average)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "aggregate(PropertyInt16 with sum from CollPropertyComp with average)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
}
@Test
public void identity() throws Exception {
parse("ESTwoKeyNav", "identity").is(Identity.class);
}
@Test
public void compute() throws Exception {
parse("ESTwoKeyNav", "compute(PropertyInt16 mul NavPropertyETKeyNavOne/PropertyInt16 as p)")
.is(Compute.class)
.goCompute(0).isAlias("p").goExpression().isBinary(BinaryOperatorKind.MUL)
.left().isMember().goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav", "compute(PropertyInt16 mul 2 as p,day(now()) as d)")
.goCompute(0).isAlias("p")
.goUp().goCompute(1).isAlias("d")
.goExpression().isMethod(MethodKind.DAY, 1).goParameter(0).isMethod(MethodKind.NOW, 0);
parseEx("ESTwoKeyNav", "compute(PropertyInt16)")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESTwoKeyNav", "compute(PropertyComp as c)")
.isExSemantic(UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES);
}
@Test
public void concat() throws Exception {
parse("ESTwoKeyNav", "concat(topcount(2,PropertyInt16),bottomcount(2,PropertyInt16))")
.is(Concat.class)
.goConcat(0).goBottomTop().isMethod(Method.TOP_COUNT)
.goUp().goUp()
.goConcat(1).goBottomTop().isMethod(Method.BOTTOM_COUNT).goNumber().isLiteral("2");
}
@Test
public void expand() throws Exception {
parse("ESTwoKeyNav", "expand(NavPropertyETKeyNavMany,filter(PropertyInt16 gt 2))")
.is(Expand.class).goExpand()
.goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true)
.goUpExpandValidator().isFilterSerialized("<<PropertyInt16> gt <2>>");
parse("ESTwoKeyNav",
"expand(NavPropertyETKeyNavMany,expand(NavPropertyETTwoKeyNavMany,filter(PropertyInt16 gt 2)))")
.is(Expand.class).goExpand().goExpand().isFilterSerialized("<<PropertyInt16> gt <2>>");
parse("ESTwoKeyNav",
"expand(NavPropertyETKeyNavMany,expand(NavPropertyETTwoKeyNavMany,filter(PropertyInt16 gt 2)),"
+ "expand(NavPropertyETTwoKeyNavOne,expand(NavPropertyETKeyNavMany)))")
.is(Expand.class).goExpand().goExpand().next().goExpand()
.goPath().first().isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
}
@Test
public void search() throws Exception {
parse("ESTwoKeyNav", "search(String)").isSearch("'String'");
}
@Test
public void filter() throws Exception {
parse("ESTwoKeyNav", "filter(PropertyInt16 gt 3)")
.is(Filter.class)
.goFilter().isBinary(BinaryOperatorKind.GT)
.left().isMember().goPath().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
}
@Test
public void bottomTop() throws Exception {
parse("ESTwoKeyNav", "topcount(2,PropertyInt16)")
.goBottomTop().isMethod(Method.TOP_COUNT)
.goNumber().isLiteralType(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte))
.isLiteral("2");
parse("ESTwoKeyNav", "topsum(2,PropertyInt16)")
.goBottomTop().isMethod(Method.TOP_SUM)
.goValue().isMember().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav", "toppercent(2,PropertyInt16)").goBottomTop().isMethod(Method.TOP_PERCENT);
parse("ESTwoKeyNav", "bottomcount(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_COUNT);
parse("ESTwoKeyNav", "bottomsum(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_SUM);
parse("ESTwoKeyNav", "bottompercent(2,PropertyInt16)").goBottomTop().isMethod(Method.BOTTOM_PERCENT);
parseEx("ESTwoKeyNav", "bottompercent(1.2,PropertyInt16)")
.isExSemantic(UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE);
parseEx("ESTwoKeyNav", "bottompercent(2,PropertyString)")
.isExSemantic(UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE);
}
@Test
public void customFunction() throws Exception {
parse("ESBaseTwoKeyNav", "Namespace1_Alias.BFCESBaseTwoKeyNavRTESBaseTwoKey()")
.isCustomFunction(FunctionProvider.nameBFCESBaseTwoKeyNavRTESBaseTwoKey);
parse("ESKeyNav(1)/CollPropertyComp", "Namespace1_Alias.BFCCollCTPrimCompRTESAllPrim()")
.isCustomFunction(FunctionProvider.nameBFCCollCTPrimCompRTESAllPrim);
parseEx("ESBaseTwoKeyNav", "BFCESBaseTwoKeyNavRTESBaseTwoKey()")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
parseEx("ESBaseTwoKeyNav", "Namespace1_Alias.BFCETBaseTwoKeyNavRTETTwoKeyNav()")
.isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND);
parseEx("ESBaseTwoKeyNav", "Namespace1_Alias.BFCCollStringRTESTwoKeyNav()")
.isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND);
parseEx("ESTwoKeyNav", "Namespace1_Alias.BFCESTwoKeyNavRTTwoKeyNav()")
.isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_MUST_USE_COLLECTIONS);
}
@Test
public void groupBy() throws Exception {
parse("ESTwoKeyNav", "groupby((PropertyString))")
.is(GroupBy.class)
.goGroupBy(0).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16))")
.is(GroupBy.class)
.goGroupBy(0).goPath().first().isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false)
.n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16,PropertyString))")
.is(GroupBy.class)
.goGroupBy(1).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString))");
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString,PropertyString))")
.goGroupBy(2).goPath().first().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
}
@Test
public void groupByAggregate() throws Exception {
parse("ESTwoKeyNav", "groupby((PropertyInt16),aggregate(PropertyInt16 with sum as s))")
.goGroupByOption().goAggregate(0).isStandardMethod(StandardMethod.SUM)
.goExpression().goPath().first().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyInt16),aggregate(PropertyInt16 with average as a))")
.goGroupByOption().goAggregate(0).isStandardMethod(StandardMethod.AVERAGE)
.goUp().goUp().goGroupBy(0).goPath()
.first().isNavProperty("NavPropertyETKeyNavOne", EntityTypeProvider.nameETKeyNav, false)
.n().isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne/PropertyInt16),"
+ "aggregate(PropertyInt16 with sum as s,PropertyInt16 with average as a))")
.goGroupByOption().goAggregate(1).isStandardMethod(StandardMethod.AVERAGE);
parse("ESTwoKeyNav", "groupby((PropertyInt16),aggregate(NavPropertyETKeyNavMany/$count as c))")
.goGroupByOption().goAggregate(0).goPath().at(1).isCount();
parse("ESTwoKeyNav", "groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
.goGroupByOption().goAggregate(0).goInlineAggregateExpression().isStandardMethod(StandardMethod.SUM)
.goUpAggregate().goPath().first()
.isNavProperty("NavPropertyETKeyNavMany", EntityTypeProvider.nameETKeyNav, true);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+ "aggregate(PropertyInt16 with sum as s))")
.goGroupBy(1).goPath().at(1).isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+ "aggregate(PropertyInt16 with sum as s from NavPropertyETKeyNavOne/PropertyInt16 with average))")
.goGroupByOption().goAggregate(0).goFrom(0).isStandardMethod(StandardMethod.AVERAGE);
parse("ESTwoKeyNav", "groupby((NavPropertyETKeyNavOne),aggregate(CollPropertyComp(PropertyInt16 with sum as s)))");
parse("ESTwoKeyNav",
"groupby((NavPropertyETTwoKeyNavOne/PropertyInt16),"
+ "topcount(2,PropertyInt16)/aggregate(PropertyInt16 with sum as s))")
.goGroupByOption()
.at(0).goBottomTop().isMethod(Method.TOP_COUNT)
.goUp().at(1).goAggregate(0).isStandardMethod(StandardMethod.SUM);
}
@Test
public void groupByRollUp() throws Exception {
parse("ESTwoKeyNav",
"groupby((rollup(NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+ "rollup(NavPropertyETKeyNavOne/NavPropertyETTwoKeyNavOne/PropertyInt16,"
+ "NavPropertyETTwoKeyNavOne/PropertyString),NavPropertyETTwoKeyNavOne/PropertyInt16),"
+ "aggregate(PropertyInt16 with sum as s))")
.goGroupBy(1).goRollup(1).goPath()
.first().isNavProperty("NavPropertyETTwoKeyNavOne", EntityTypeProvider.nameETTwoKeyNav, false)
.n().isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
parse("ESTwoKeyNav",
"groupby((rollup($all,NavPropertyETKeyNavOne/PropertyInt16,NavPropertyETKeyNavOne/PropertyString),"
+ "NavPropertyETTwoKeyNavOne/PropertyString),"
+ "aggregate(PropertyInt16 with sum as s from NavPropertyETTwoKeyNavOne/PropertyInt16 with average "
+ "from NavPropertyETTwoKeyNavOne/PropertyString with average))")
.goGroupBy(0).isRollupAll().goUp().goGroupByOption().goAggregate(0).goFrom(1)
.isStandardMethod(StandardMethod.AVERAGE).goExpression().goPath().at(1)
.isPrimitiveProperty("PropertyString", PropertyProvider.nameString, false);
}
@Test
public void groupBySpecial() throws Exception {
parse("ESTwoKeyNav", "groupby((NavPropertyETTwoKeyNavOne/PropertyInt16),aggregate(customAggregate))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(0)
.goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty);
parse("ESTwoKeyNav",
"groupby((PropertyString),aggregate(NavPropertyETKeyNavMany/$count as c,"
+ "NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(0).isAlias("c").goPath().at(1).isCount();
parse("ESTwoKeyNav",
"groupby((PropertyString),aggregate(NavPropertyETKeyNavMany($count as c),"
+ "NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(0).goInlineAggregateExpression()
.isAlias("c").goPath().first().isCount();
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyString),"
+ "aggregate(PropertyInt16 with sum as s,customAggregate))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(1).isStandardMethod(null).isAlias(null)
.goPath().first().isUriPathInfoKind(UriResourceKind.primitiveProperty);
parse("ESTwoKeyNav",
"groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s),"
+ "NavPropertyETKeyNavMany/customAggregate))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(1)
.goPath().at(1).isUriPathInfoKind(UriResourceKind.primitiveProperty);
parse("ESTwoKeyNav",
"groupby((PropertyString),aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s),"
+ "NavPropertyETKeyNavMany(PropertyInt16 with average as a)))")
.is(GroupBy.class)
.goGroupByOption().goAggregate(0).goInlineAggregateExpression()
.isStandardMethod(StandardMethod.SUM).isAlias("s")
.goUpAggregate().goUp().goAggregate(1).goInlineAggregateExpression()
.isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
}
@Test
public void sequence() throws Exception {
parse("ESTwoKeyNav", "identity/identity/identity")
.at(0).is(Identity.class).at(1).is(Identity.class).at(2).is(Identity.class);
parse("ESTwoKeyNav", "filter(PropertyInt16 le 1)/aggregate(PropertyInt16 with sum as s)")
.at(0).is(Filter.class)
.at(1).is(Aggregate.class).goAggregate(0).isStandardMethod(StandardMethod.SUM).isAlias("s");
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne),aggregate(PropertyInt16 with sum as s))/"
+ "aggregate(s with average as a)")
.at(1).goAggregate(0).isStandardMethod(StandardMethod.AVERAGE).isAlias("a");
parse("ESTwoKeyNav",
"filter(PropertyInt16 ge 1)/"
+ "groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))")
.at(0).is(Filter.class)
.at(1).is(GroupBy.class);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+ "filter(s ge 10)/concat(identity,groupby((NavPropertyETKeyNavOne/PropertyString),"
+ "aggregate(s with sum as t)))")
.at(0).is(GroupBy.class)
.at(1).is(Filter.class)
.at(2).is(Concat.class).goConcat(0).is(Identity.class);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+ "filter(s ge 10)/groupby((rollup(NavPropertyETKeyNavOne/PropertyString,"
+ "NavPropertyETKeyNavOne/PropertyCompAllPrim/PropertyDuration)),aggregate(s with sum as t))")
.at(0).is(GroupBy.class)
.at(1).is(Filter.class)
.at(2).is(GroupBy.class).goGroupBy(0).goRollup(1).goPath().at(2)
.isPrimitiveProperty("PropertyDuration", PropertyProvider.nameDuration, false);
parse("ESTwoKeyNav",
"groupby((NavPropertyETKeyNavOne/PropertyString),aggregate(PropertyInt16 with sum as s))/"
+ "concat(filter(s ge 10),groupby((NavPropertyETKeyNavOne/PropertyString),"
+ "aggregate(s with sum as t)))")
.at(0).is(GroupBy.class)
.at(1).is(Concat.class).goConcat(1).is(GroupBy.class);
parse("ESTwoKeyNav",
"filter(PropertyInt16 eq 1)/expand(NavPropertyETKeyNavMany,filter(not PropertyCompAllPrim/PropertyBoolean))/"
+ "groupby((NavPropertyETKeyNavOne/PropertyInt16),"
+ "aggregate(NavPropertyETKeyNavMany(PropertyInt16 with sum as s)))")
.at(0).is(Filter.class)
.at(1).is(Expand.class)
.at(2).is(GroupBy.class);
}
private ApplyValidator parse(final String path, final String apply)
throws UriParserException, UriValidationException {
final UriInfo uriInfo = new Parser(edm, odata).parseUri(path, "$apply=" + apply, null);
return new ApplyValidator(uriInfo.getApplyOption());
}
private TestUriValidator parseEx(final String path, final String apply) {
return new TestUriValidator().setEdm(edm).runEx(path, "$apply=" + apply);
}
private final class ApplyValidator implements TestValidator {
private final ApplyOption applyOption;
private final ApplyValidator previous;
private ApplyItem applyItem;
protected ApplyValidator(final ApplyOption applyOption) {
this(applyOption, null);
}
private ApplyValidator(final ApplyOption applyOption, final ApplyValidator previous) {
this.applyOption = applyOption;
this.previous = previous;
at(0);
}
public ApplyValidator at(final int index) {
assertTrue(index < applyOption.getApplyItems().size());
applyItem = applyOption.getApplyItems().get(index);
return this;
}
public ApplyValidator is(final Class<? extends ApplyItem> cls) {
assertNotNull(applyItem);
assertTrue(cls.isAssignableFrom(applyItem.getClass()));
return this;
}
public AggregateValidator goAggregate(final int index) {
is(Aggregate.class);
assertTrue(index < ((Aggregate) applyItem).getExpressions().size());
return new AggregateValidator(((Aggregate) applyItem).getExpressions().get(index), this);
}
public ExpandValidator goExpand() {
is(Expand.class);
return new ExpandValidator().setUpValidator(this).setExpand(((Expand) applyItem).getExpandOption());
}
public FilterValidator goFilter() {
is(Filter.class);
return new FilterValidator().setFilter(((Filter) applyItem).getFilterOption());
}
public BottomTopValidator goBottomTop() {
is(BottomTop.class);
return new BottomTopValidator((BottomTop) applyItem, this);
}
public ApplyValidator isCustomFunction(final FullQualifiedName function) {
is(CustomFunction.class);
assertEquals(function, ((CustomFunction) applyItem).getFunction().getFullQualifiedName());
return this;
}
public ApplyValidator isSearch(final String serializedSearch) {
is(Search.class);
assertEquals(serializedSearch, ((Search) applyItem).getSearchOption().getSearchExpression().toString());
return this;
}
public ApplyValidator goConcat(final int index) {
is(Concat.class);
assertTrue(index < ((Concat) applyItem).getApplyOptions().size());
return new ApplyValidator(((Concat) applyItem).getApplyOptions().get(index), this);
}
public ComputeValidator goCompute(final int index) {
is(Compute.class);
assertTrue(index < ((Compute) applyItem).getExpressions().size());
return new ComputeValidator(((Compute) applyItem).getExpressions().get(index), this);
}
public GroupByValidator goGroupBy(final int index) {
is(GroupBy.class);
assertTrue(index < ((GroupBy) applyItem).getGroupByItems().size());
return new GroupByValidator(((GroupBy) applyItem).getGroupByItems().get(index), this);
}
public ApplyValidator goGroupByOption() {
is(GroupBy.class);
assertNotNull(((GroupBy) applyItem).getApplyOption());
return new ApplyValidator(((GroupBy) applyItem).getApplyOption(), this);
}
public ApplyValidator goUp() {
return previous;
}
}
private final class AggregateValidator implements TestValidator {
private final AggregateExpression aggregateExpression;
private final TestValidator previous;
protected AggregateValidator(final AggregateExpression aggregateExpression, final TestValidator previous) {
this.aggregateExpression = aggregateExpression;
this.previous = previous;
}
public AggregateValidator isStandardMethod(final AggregateExpression.StandardMethod method) {
assertNotNull(aggregateExpression);
assertEquals(method, aggregateExpression.getStandardMethod());
return this;
}
public AggregateValidator isCustomMethod(final FullQualifiedName method) {
assertNotNull(aggregateExpression);
assertEquals(method, aggregateExpression.getCustomMethod());
return this;
}
public AggregateValidator isAlias(final String alias) {
assertNotNull(aggregateExpression);
assertEquals(alias, aggregateExpression.getAlias());
return this;
}
public FilterValidator goExpression() {
assertNotNull(aggregateExpression);
assertNotNull(aggregateExpression.getExpression());
return new FilterValidator().setValidator(this).setEdm(edm)
.setExpression(aggregateExpression.getExpression());
}
public ResourceValidator goPath() {
assertNotNull(aggregateExpression);
assertFalse(aggregateExpression.getPath().isEmpty());
UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
for (final UriResource segment : aggregateExpression.getPath()) {
resource.addResourcePart(segment);
}
return new ResourceValidator().setUpValidator(this).setEdm(edm).setUriInfoPath(resource);
}
public AggregateValidator goInlineAggregateExpression() {
return new AggregateValidator(aggregateExpression.getInlineAggregateExpression(), this);
}
public AggregateValidator goFrom(final int index) {
assertTrue(index < aggregateExpression.getFrom().size());
return new AggregateValidator(aggregateExpression.getFrom().get(index), this);
}
public AggregateValidator goUpAggregate() {
return (AggregateValidator) previous;
}
public ApplyValidator goUp() {
return (ApplyValidator) previous;
}
}
private final class BottomTopValidator implements TestValidator {
private final BottomTop item;
private final ApplyValidator previous;
private BottomTopValidator(final BottomTop item, final ApplyValidator previous) {
this.item = item;
this.previous = previous;
}
public BottomTopValidator isMethod(final BottomTop.Method method) {
assertEquals(method, item.getMethod());
return this;
}
public FilterValidator goNumber() {
assertNotNull(item.getNumber());
return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getNumber());
}
public FilterValidator goValue() {
assertNotNull(item.getValue());
return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getValue());
}
public ApplyValidator goUp() {
return previous;
}
}
private final class ComputeValidator implements TestValidator {
private final ComputeExpression item;
private final ApplyValidator previous;
private ComputeValidator(final ComputeExpression item, final ApplyValidator previous) {
this.item = item;
this.previous = previous;
}
public ComputeValidator isAlias(final String alias) {
assertEquals(alias, item.getAlias());
return this;
}
public FilterValidator goExpression() {
assertNotNull(item.getExpression());
return new FilterValidator().setValidator(this).setEdm(edm).setExpression(item.getExpression());
}
public ApplyValidator goUp() {
return previous;
}
}
private final class GroupByValidator implements TestValidator {
private final GroupByItem item;
private final TestValidator previous;
private GroupByValidator(final GroupByItem item, final TestValidator previous) {
this.item = item;
this.previous = previous;
}
public ResourceValidator goPath() {
assertFalse(item.getPath().isEmpty());
UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
for (final UriResource segment : item.getPath()) {
resource.addResourcePart(segment);
}
return new ResourceValidator().setUpValidator(this).setEdm(edm).setUriInfoPath(resource);
}
public GroupByValidator isRollupAll() {
assertTrue(item.isRollupAll());
return this;
}
public GroupByValidator goRollup(final int index) {
assertTrue(index < item.getRollup().size());
return new GroupByValidator(item.getRollup().get(index), this);
}
public ApplyValidator goUp() {
return (ApplyValidator) previous;
}
}
}

View File

@ -88,16 +88,17 @@ public class UriValidatorTest {
private static final String QO_SKIP = "$skip=3";
private static final String QO_SKIPTOKEN = "$skiptoken=123";
private static final String QO_TOP = "$top=1";
private static final String QO_APPLY = "$apply=identity";
private final String[][] urisWithValidSystemQueryOptions = {
{ URI_ALL, QO_FILTER }, { URI_ALL, QO_FORMAT }, { URI_ALL, QO_EXPAND }, { URI_ALL, QO_COUNT },
{ URI_ALL, QO_ORDERBY }, { URI_ALL, QO_SEARCH }, { URI_ALL, QO_SELECT }, { URI_ALL, QO_SKIP },
{ URI_ALL, QO_SKIPTOKEN }, { URI_ALL, QO_TOP },
{ URI_ALL, QO_SKIPTOKEN }, { URI_ALL, QO_TOP }, { URI_ALL, QO_APPLY },
{ URI_CROSSJOIN, QO_FILTER }, { URI_CROSSJOIN, QO_FORMAT },
{ URI_CROSSJOIN, QO_EXPAND }, { URI_CROSSJOIN, QO_COUNT }, { URI_CROSSJOIN, QO_ORDERBY },
{ URI_CROSSJOIN, QO_SEARCH }, { URI_CROSSJOIN, QO_SELECT }, { URI_CROSSJOIN, QO_SKIP },
{ URI_CROSSJOIN, QO_SKIPTOKEN }, { URI_CROSSJOIN, QO_TOP },
{ URI_CROSSJOIN, QO_SKIPTOKEN }, { URI_CROSSJOIN, QO_TOP }, { URI_CROSSJOIN, QO_APPLY },
{ URI_ENTITY_ID, QO_ID, QO_FORMAT }, { URI_ENTITY_ID, QO_ID }, { URI_ENTITY_ID, QO_ID, QO_EXPAND },
{ URI_ENTITY_ID, QO_ID, QO_SELECT },
@ -109,7 +110,7 @@ public class UriValidatorTest {
{ URI_ENTITY_SET, QO_FILTER }, { URI_ENTITY_SET, QO_FORMAT }, { URI_ENTITY_SET, QO_EXPAND },
{ URI_ENTITY_SET, QO_COUNT }, { URI_ENTITY_SET, QO_ORDERBY }, { URI_ENTITY_SET, QO_SEARCH },
{ URI_ENTITY_SET, QO_SELECT }, { URI_ENTITY_SET, QO_SKIP }, { URI_ENTITY_SET, QO_SKIPTOKEN },
{ URI_ENTITY_SET, QO_TOP },
{ URI_ENTITY_SET, QO_TOP }, { URI_ENTITY_SET, QO_APPLY },
{ URI_ENTITY_SET_COUNT, QO_FILTER }, { URI_ENTITY_SET_COUNT, QO_SEARCH },
@ -127,7 +128,7 @@ public class UriValidatorTest {
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_EXPAND }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_COUNT },
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_ORDERBY }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SELECT },
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_SKIP }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SKIPTOKEN },
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_TOP },
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_TOP }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_APPLY },
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_FILTER },
@ -149,12 +150,12 @@ public class UriValidatorTest {
{ URI_NAV_ENTITY_SET, QO_FILTER }, { URI_NAV_ENTITY_SET, QO_FORMAT }, { URI_NAV_ENTITY_SET, QO_EXPAND },
{ URI_NAV_ENTITY_SET, QO_COUNT }, { URI_NAV_ENTITY_SET, QO_ORDERBY },
{ URI_NAV_ENTITY_SET, QO_SEARCH }, { URI_NAV_ENTITY_SET, QO_SELECT }, { URI_NAV_ENTITY_SET, QO_SKIP },
{ URI_NAV_ENTITY_SET, QO_SKIPTOKEN }, { URI_NAV_ENTITY_SET, QO_TOP },
{ URI_NAV_ENTITY_SET, QO_SKIPTOKEN }, { URI_NAV_ENTITY_SET, QO_TOP }, { URI_NAV_ENTITY_SET, QO_APPLY },
{ URI_FI_ENTITY_SET, QO_FILTER }, { URI_FI_ENTITY_SET, QO_FORMAT }, { URI_FI_ENTITY_SET, QO_EXPAND },
{ URI_FI_ENTITY_SET, QO_COUNT }, { URI_FI_ENTITY_SET, QO_ORDERBY }, { URI_FI_ENTITY_SET, QO_SEARCH },
{ URI_FI_ENTITY_SET, QO_SELECT }, { URI_FI_ENTITY_SET, QO_SKIP }, { URI_FI_ENTITY_SET, QO_SKIPTOKEN },
{ URI_FI_ENTITY_SET, QO_TOP },
{ URI_FI_ENTITY_SET, QO_TOP }, { URI_FI_ENTITY_SET, QO_APPLY },
{ URI_FI_ENTITY, QO_FORMAT }, { URI_FI_ENTITY, QO_EXPAND }, { URI_FI_ENTITY, QO_SELECT },
{ URI_FI_ENTITY_SET_KEY, QO_FORMAT }, { URI_FI_ENTITY_SET_KEY, QO_EXPAND }, { URI_FI_ENTITY_SET_KEY, QO_SELECT },
@ -170,22 +171,23 @@ public class UriValidatorTest {
{ URI_BATCH, QO_FILTER }, { URI_BATCH, QO_FORMAT }, { URI_BATCH, QO_ID }, { URI_BATCH, QO_EXPAND },
{ URI_BATCH, QO_COUNT }, { URI_BATCH, QO_ORDERBY }, { URI_BATCH, QO_SEARCH }, { URI_BATCH, QO_SELECT },
{ URI_BATCH, QO_SKIP }, { URI_BATCH, QO_SKIPTOKEN }, { URI_BATCH, QO_TOP },
{ URI_BATCH, QO_SKIP }, { URI_BATCH, QO_SKIPTOKEN }, { URI_BATCH, QO_TOP }, { URI_BATCH, QO_APPLY },
{ URI_CROSSJOIN, QO_ID },
{ URI_ENTITY_ID, QO_ID, QO_FILTER },
{ URI_ENTITY_ID, QO_ID, QO_COUNT }, { URI_ENTITY_ID, QO_ORDERBY }, { URI_ENTITY_ID, QO_SEARCH },
{ URI_ENTITY_ID, QO_ID, QO_COUNT }, { URI_ENTITY_ID, QO_ID, QO_ORDERBY }, { URI_ENTITY_ID, QO_ID, QO_SEARCH },
{ URI_ENTITY_ID, QO_ID, QO_SKIP }, { URI_ENTITY_ID, QO_ID, QO_SKIPTOKEN }, { URI_ENTITY_ID, QO_ID, QO_TOP },
{ URI_ENTITY_ID, QO_ID, QO_APPLY },
{ URI_METADATA, QO_FILTER }, { URI_METADATA, QO_ID }, { URI_METADATA, QO_EXPAND },
{ URI_METADATA, QO_COUNT }, { URI_METADATA, QO_ORDERBY }, { URI_METADATA, QO_SEARCH },
{ URI_METADATA, QO_SELECT }, { URI_METADATA, QO_SKIP }, { URI_METADATA, QO_SKIPTOKEN },
{ URI_METADATA, QO_TOP },
{ URI_METADATA, QO_TOP }, { URI_METADATA, QO_APPLY },
{ URI_SERVICE, QO_FILTER }, { URI_SERVICE, QO_ID }, { URI_SERVICE, QO_EXPAND }, { URI_SERVICE, QO_COUNT },
{ URI_SERVICE, QO_ORDERBY }, { URI_SERVICE, QO_SEARCH }, { URI_SERVICE, QO_SELECT },
{ URI_SERVICE, QO_SKIP }, { URI_SERVICE, QO_SKIPTOKEN }, { URI_SERVICE, QO_TOP },
{ URI_SERVICE, QO_SKIP }, { URI_SERVICE, QO_SKIPTOKEN }, { URI_SERVICE, QO_TOP }, { URI_SERVICE, QO_APPLY },
{ URI_ENTITY_SET, QO_ID },
@ -193,26 +195,29 @@ public class UriValidatorTest {
{ URI_ENTITY_SET_COUNT, QO_EXPAND }, { URI_ENTITY_SET_COUNT, QO_COUNT },
{ URI_ENTITY_SET_COUNT, QO_ORDERBY },
{ URI_ENTITY_SET_COUNT, QO_SELECT }, { URI_ENTITY_SET_COUNT, QO_SKIP }, { URI_ENTITY_SET_COUNT, QO_SKIPTOKEN },
{ URI_ENTITY_SET_COUNT, QO_TOP },
{ URI_ENTITY_SET_COUNT, QO_TOP }, { URI_ENTITY_SET_COUNT, QO_APPLY },
{ URI_ENTITY, QO_FILTER }, { URI_ENTITY, QO_ID }, { URI_ENTITY, QO_COUNT }, { URI_ENTITY, QO_ORDERBY },
{ URI_ENTITY, QO_SEARCH }, { URI_ENTITY, QO_SKIP }, { URI_ENTITY, QO_SKIPTOKEN }, { URI_ENTITY, QO_TOP },
{ URI_ENTITY, QO_APPLY },
{ URI_MEDIA_STREAM, QO_FILTER }, { URI_MEDIA_STREAM, QO_FORMAT }, { URI_MEDIA_STREAM, QO_ID },
{ URI_MEDIA_STREAM, QO_EXPAND }, { URI_MEDIA_STREAM, QO_COUNT }, { URI_MEDIA_STREAM, QO_ORDERBY },
{ URI_MEDIA_STREAM, QO_SEARCH }, { URI_MEDIA_STREAM, QO_SELECT }, { URI_MEDIA_STREAM, QO_SKIP },
{ URI_MEDIA_STREAM, QO_SKIPTOKEN }, { URI_MEDIA_STREAM, QO_TOP },
{ URI_MEDIA_STREAM, QO_SKIPTOKEN }, { URI_MEDIA_STREAM, QO_TOP }, { URI_MEDIA_STREAM, QO_APPLY },
{ URI_REFERENCES, QO_ID }, { URI_REFERENCES, QO_EXPAND }, { URI_REFERENCES, QO_SELECT },
{ URI_REFERENCES, QO_APPLY },
{ URI_REFERENCE, QO_FILTER }, { URI_REFERENCE, QO_ID }, { URI_REFERENCE, QO_EXPAND },
{ URI_REFERENCE, QO_COUNT }, { URI_REFERENCE, QO_ORDERBY }, { URI_REFERENCE, QO_SEARCH },
{ URI_REFERENCE, QO_SELECT }, { URI_REFERENCE, QO_SKIP }, { URI_REFERENCE, QO_SKIPTOKEN },
{ URI_REFERENCE, QO_TOP },
{ URI_REFERENCE, QO_TOP }, { URI_REFERENCE, QO_APPLY },
{ URI_PROPERTY_COMPLEX, QO_FILTER }, { URI_PROPERTY_COMPLEX, QO_ID }, { URI_PROPERTY_COMPLEX, QO_COUNT },
{ URI_PROPERTY_COMPLEX, QO_ORDERBY }, { URI_PROPERTY_COMPLEX, QO_SEARCH },
{ URI_PROPERTY_COMPLEX, QO_SKIP }, { URI_PROPERTY_COMPLEX, QO_SKIPTOKEN }, { URI_PROPERTY_COMPLEX, QO_TOP },
{ URI_PROPERTY_COMPLEX, QO_APPLY },
{ URI_PROPERTY_COMPLEX_COLLECTION, QO_ID }, { URI_PROPERTY_COMPLEX_COLLECTION, QO_SEARCH },
@ -221,16 +226,17 @@ public class UriValidatorTest {
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_COUNT }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_ORDERBY },
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SEARCH }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SELECT },
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SKIP }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_SKIPTOKEN },
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_TOP },
{ URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_TOP }, { URI_PROPERTY_COMPLEX_COLLECTION_COUNT, QO_APPLY },
{ URI_PROPERTY_PRIMITIVE, QO_FILTER }, { URI_PROPERTY_PRIMITIVE, QO_ID }, { URI_PROPERTY_PRIMITIVE, QO_EXPAND },
{ URI_PROPERTY_PRIMITIVE, QO_COUNT }, { URI_PROPERTY_PRIMITIVE, QO_ORDERBY },
{ URI_PROPERTY_PRIMITIVE, QO_SEARCH }, { URI_PROPERTY_PRIMITIVE, QO_SELECT },
{ URI_PROPERTY_PRIMITIVE, QO_SKIP }, { URI_PROPERTY_PRIMITIVE, QO_SKIPTOKEN },
{ URI_PROPERTY_PRIMITIVE, QO_TOP },
{ URI_PROPERTY_PRIMITIVE, QO_TOP }, { URI_PROPERTY_PRIMITIVE, QO_APPLY },
{ URI_PROPERTY_PRIMITIVE_COLLECTION, QO_ID }, { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_EXPAND },
{ URI_PROPERTY_PRIMITIVE_COLLECTION, QO_SEARCH }, { URI_PROPERTY_PRIMITIVE_COLLECTION, QO_SELECT },
{ URI_PROPERTY_PRIMITIVE_COLLECTION, QO_APPLY },
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_FORMAT },
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_ID }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_EXPAND },
@ -238,20 +244,22 @@ public class UriValidatorTest {
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_ORDERBY }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SEARCH },
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SELECT }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SKIP },
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_SKIPTOKEN }, { URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_TOP },
{ URI_PROPERTY_PRIMITIVE_COLLECTION_COUNT, QO_APPLY },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_FILTER }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_ID },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_EXPAND }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_COUNT },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_ORDERBY }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_SEARCH },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_SELECT }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_SKIP },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_SKIPTOKEN }, { URI_PROPERTY_PRIMITIVE_VALUE, QO_TOP },
{ URI_PROPERTY_PRIMITIVE_VALUE, QO_APPLY },
{ URI_SINGLETON, QO_FILTER }, { URI_SINGLETON, QO_ID }, { URI_SINGLETON, QO_COUNT },
{ URI_SINGLETON, QO_ORDERBY }, { URI_SINGLETON, QO_SEARCH }, { URI_SINGLETON, QO_SKIP },
{ URI_SINGLETON, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP },
{ URI_SINGLETON, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP }, { URI_SINGLETON, QO_APPLY },
{ URI_NAV_ENTITY, QO_FILTER }, { URI_NAV_ENTITY, QO_ID }, { URI_NAV_ENTITY, QO_COUNT },
{ URI_NAV_ENTITY, QO_ORDERBY }, { URI_NAV_ENTITY, QO_SEARCH }, { URI_NAV_ENTITY, QO_SKIP },
{ URI_NAV_ENTITY, QO_SKIPTOKEN }, { URI_SINGLETON, QO_TOP },
{ URI_NAV_ENTITY, QO_SKIPTOKEN }, { URI_NAV_ENTITY, QO_TOP }, { URI_NAV_ENTITY, QO_APPLY },
{ URI_NAV_ENTITY_SET, QO_ID },
@ -262,7 +270,8 @@ public class UriValidatorTest {
{ URI_FI_ENTITY, QO_SKIPTOKEN }, { URI_FI_ENTITY, QO_TOP },
{ URI_FI_ENTITY_SET_KEY, QO_FILTER }, { URI_FI_ENTITY_SET_KEY, QO_ID }, { URI_FI_ENTITY_SET_KEY, QO_COUNT },
{ URI_FI_ENTITY_SET_KEY, QO_ORDERBY }, { URI_FI_ENTITY_SET_KEY, QO_SEARCH },
{ URI_FI_ENTITY_SET_KEY, QO_SKIP }, { URI_FI_ENTITY_SET_KEY, QO_SKIPTOKEN }, { URI_FI_ENTITY_SET_KEY, QO_TOP }
{ URI_FI_ENTITY_SET_KEY, QO_SKIP }, { URI_FI_ENTITY_SET_KEY, QO_SKIPTOKEN }, { URI_FI_ENTITY_SET_KEY, QO_TOP },
{ URI_FI_ENTITY_SET_KEY, QO_APPLY }
};
private final String[][] actionWithValidSystemQueryOptions = {
@ -282,14 +291,14 @@ public class UriValidatorTest {
{ URI_ACTION_COLL_CT, QO_EXPAND }, { URI_ACTION_COLL_CT, QO_COUNT },
{ URI_ACTION_COLL_CT, QO_ORDERBY }, { URI_ACTION_COLL_CT, QO_SELECT },
{ URI_ACTION_COLL_CT, QO_SKIP }, { URI_ACTION_COLL_CT, QO_SKIPTOKEN },
{ URI_ACTION_COLL_CT, QO_TOP },
{ URI_ACTION_COLL_CT, QO_TOP }, { URI_ACTION_COLL_CT, QO_APPLY },
// EntityReturnType
{ URI_ACTION_ENTITY, QO_FORMAT }, { URI_ACTION_ENTITY, QO_EXPAND }, { URI_ACTION_ENTITY, QO_SELECT },
// EntityCollectionReturnType
{ URI_ACTION_ES, QO_FORMAT }, { URI_ACTION_ES, QO_FILTER },
{ URI_ACTION_ES, QO_COUNT }, { URI_ACTION_ES, QO_ORDERBY }, { URI_ACTION_ES, QO_SEARCH },
{ URI_ACTION_ES, QO_SELECT }, { URI_ACTION_ES, QO_SKIP }, { URI_ACTION_ES, QO_SKIPTOKEN },
{ URI_ACTION_ES, QO_TOP }
{ URI_ACTION_ES, QO_TOP }, { URI_ACTION_ES, QO_APPLY }
};
private final String[][] actionsWithNotValidSystemQueryOptions = {
@ -298,26 +307,28 @@ public class UriValidatorTest {
{ URI_ACTION_NO_RETURN, QO_EXPAND }, { URI_ACTION_NO_RETURN, QO_COUNT },
{ URI_ACTION_NO_RETURN, QO_ORDERBY }, { URI_ACTION_NO_RETURN, QO_SEARCH },
{ URI_ACTION_NO_RETURN, QO_SELECT }, { URI_ACTION_NO_RETURN, QO_SKIP },
{ URI_ACTION_NO_RETURN, QO_SKIPTOKEN }, { URI_ACTION_NO_RETURN, QO_TOP },
{ URI_ACTION_NO_RETURN, QO_SKIPTOKEN }, { URI_ACTION_NO_RETURN, QO_TOP }, { URI_ACTION_NO_RETURN, QO_APPLY },
// PrimReturnType
{ URI_ACTION_PRIM, QO_FILTER }, { URI_ACTION_PRIM, QO_ID },
{ URI_ACTION_PRIM, QO_EXPAND }, { URI_ACTION_PRIM, QO_COUNT },
{ URI_ACTION_PRIM, QO_ORDERBY }, { URI_ACTION_PRIM, QO_SEARCH },
{ URI_ACTION_PRIM, QO_SELECT }, { URI_ACTION_PRIM, QO_SKIP },
{ URI_ACTION_PRIM, QO_SKIPTOKEN }, { URI_ACTION_PRIM, QO_TOP },
{ URI_ACTION_PRIM, QO_SKIPTOKEN }, { URI_ACTION_PRIM, QO_TOP }, { URI_ACTION_PRIM, QO_APPLY },
// PrimCollectionReturnType
{ URI_ACTION_COLL_PRIM, QO_ID }, { URI_ACTION_COLL_PRIM, QO_EXPAND },
{ URI_ACTION_COLL_PRIM, QO_SEARCH }, { URI_ACTION_COLL_PRIM, QO_SELECT },
{ URI_ACTION_COLL_PRIM, QO_SEARCH }, { URI_ACTION_COLL_PRIM, QO_SELECT }, { URI_ACTION_COLL_PRIM, QO_APPLY },
// ComplexReturnType
{ URI_ACTION_CT, QO_FILTER }, { URI_ACTION_CT, QO_ID }, { URI_ACTION_CT, QO_COUNT },
{ URI_ACTION_CT, QO_ORDERBY }, { URI_ACTION_CT, QO_SEARCH },
{ URI_ACTION_CT, QO_SKIP }, { URI_ACTION_CT, QO_SKIPTOKEN }, { URI_ACTION_CT, QO_TOP },
{ URI_ACTION_CT, QO_APPLY },
// ComplexCollectionReturnType
{ URI_ACTION_COLL_CT, QO_ID }, { URI_ACTION_COLL_CT, QO_SEARCH },
// EntityReturnType
{ URI_ACTION_ENTITY, QO_FILTER }, { URI_ACTION_ENTITY, QO_ID }, { URI_ACTION_ENTITY, QO_COUNT },
{ URI_ACTION_ENTITY, QO_ORDERBY }, { URI_ACTION_ENTITY, QO_SEARCH },
{ URI_ACTION_ENTITY, QO_SKIP }, { URI_ACTION_ENTITY, QO_SKIPTOKEN }, { URI_ACTION_ENTITY, QO_TOP },
{ URI_ACTION_ENTITY, QO_APPLY },
// EntityCollectionReturnType
{ URI_ACTION_ES, QO_ID }
};