blob: ca6498aab84216c605becb4b6ab74b6419629665 [file] [log] [blame]
/*
* 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.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmAction;
import org.apache.olingo.commons.api.edm.EdmActionImport;
import org.apache.olingo.commons.api.edm.EdmEntityContainer;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.commons.api.edm.EdmFunctionImport;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmSingleton;
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.uri.UriParameter;
import org.apache.olingo.server.api.uri.UriResource;
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.AliasQueryOption;
import org.apache.olingo.server.core.uri.UriResourceActionImpl;
import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceCountImpl;
import org.apache.olingo.server.core.uri.UriResourceEntitySetImpl;
import org.apache.olingo.server.core.uri.UriResourceFunctionImpl;
import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceRefImpl;
import org.apache.olingo.server.core.uri.UriResourceSingletonImpl;
import org.apache.olingo.server.core.uri.UriResourceTypedImpl;
import org.apache.olingo.server.core.uri.UriResourceValueImpl;
import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl;
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
public class ResourcePathParser {
private final Edm edm;
private final EdmEntityContainer edmEntityContainer;
private final Map<String, AliasQueryOption> aliases;
private UriTokenizer tokenizer;
public ResourcePathParser(final Edm edm, final Map<String, AliasQueryOption> aliases) {
this.edm = edm;
this.aliases = aliases;
edmEntityContainer = edm.getEntityContainer();
}
public UriResource parsePathSegment(final String pathSegment, UriResource previous)
throws UriParserException, UriValidationException {
tokenizer = new UriTokenizer(pathSegment);
// The order is important.
// A qualified name should not be parsed as identifier and let the tokenizer halt at '.'.
if (previous == null) {
if (tokenizer.next(TokenKind.QualifiedName)) {
throw new UriParserSemanticException("The initial segment must not be namespace-qualified.",
UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT,
new FullQualifiedName(tokenizer.getText()).getNamespace());
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
return leadingResourcePathSegment();
}
} else {
if (tokenizer.next(TokenKind.REF)) {
return ref(previous);
} else if (tokenizer.next(TokenKind.VALUE)) {
return value(previous);
} else if (tokenizer.next(TokenKind.COUNT)) {
return count(previous);
} else if (tokenizer.next(TokenKind.QualifiedName)) {
return boundOperationOrTypeCast(previous);
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
return navigationOrProperty(previous);
}
}
throw new UriParserSyntaxException("Unexpected start of resource-path segment.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
public EdmEntityType parseDollarEntityTypeCast(final String pathSegment) throws UriParserException {
tokenizer = new UriTokenizer(pathSegment);
ParserHelper.requireNext(tokenizer, TokenKind.QualifiedName);
final String name = tokenizer.getText();
ParserHelper.requireTokenEnd(tokenizer);
final EdmEntityType type = edm.getEntityType(new FullQualifiedName(name));
if (type == null) {
throw new UriParserSemanticException("Type '" + name + "' not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name);
}
return type;
}
public List<String> parseCrossjoinSegment(final String pathSegment) throws UriParserException {
tokenizer = new UriTokenizer(pathSegment);
ParserHelper.requireNext(tokenizer, TokenKind.CROSSJOIN);
ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
// At least one entity-set name is mandatory. Try to fetch all.
List<String> entitySetNames = new ArrayList<>();
do {
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
final String name = tokenizer.getText();
final EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(name);
if (edmEntitySet == null) {
throw new UriParserSemanticException("Expected Entity Set Name.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, name);
} else {
entitySetNames.add(name);
}
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
ParserHelper.requireTokenEnd(tokenizer);
return entitySetNames;
}
private UriResource ref(final UriResource previous) throws UriParserException {
ParserHelper.requireTokenEnd(tokenizer);
requireTyped(previous, "$ref");
if (((UriResourcePartTyped) previous).getType() instanceof EdmEntityType) {
return new UriResourceRefImpl();
} else {
throw new UriParserSemanticException("$ref is only allowed on entity types.",
UriParserSemanticException.MessageKeys.ONLY_FOR_ENTITY_TYPES, "$ref");
}
}
private UriResource value(final UriResource previous) throws UriParserException {
ParserHelper.requireTokenEnd(tokenizer);
requireTyped(previous, "$value");
if (!((UriResourcePartTyped) previous).isCollection()) {
requireMediaResourceInCaseOfEntity(previous);
return new UriResourceValueImpl();
} else {
throw new UriParserSemanticException("$value is only allowed on typed path segments.",
UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS, "$value");
}
}
private void requireMediaResourceInCaseOfEntity(UriResource resource) throws UriParserSemanticException {
// If the resource is an entity or navigatio
if (resource instanceof UriResourceEntitySet && !((UriResourceEntitySet) resource).getEntityType().hasStream()
|| resource instanceof UriResourceNavigation
&& !((EdmEntityType) ((UriResourceNavigation) resource).getType()).hasStream()) {
throw new UriParserSemanticException("$value on entity is only allowed on media resources.",
UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE, resource.getSegmentValue());
}
// Functions can also deliver an entity. In this case we have to check if the returned entity is a media resource
if (resource instanceof UriResourceFunction) {
EdmType returnType = ((UriResourceFunction) resource).getFunction().getReturnType().getType();
//Collection check is above so not needed here
if (returnType instanceof EdmEntityType && !((EdmEntityType) returnType).hasStream()) {
throw new UriParserSemanticException("$value on returned entity is only allowed on media resources.",
UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE, resource.getSegmentValue());
}
}
}
private UriResource count(final UriResource previous) throws UriParserException {
ParserHelper.requireTokenEnd(tokenizer);
requireTyped(previous, "$count");
if (((UriResourcePartTyped) previous).isCollection()) {
return new UriResourceCountImpl();
} else {
throw new UriParserSemanticException("$count is only allowed on collections.",
UriParserSemanticException.MessageKeys.ONLY_FOR_COLLECTIONS, "$count");
}
}
private UriResource leadingResourcePathSegment() throws UriParserException, UriValidationException {
final String oDataIdentifier = tokenizer.getText();
final EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(oDataIdentifier);
if (edmEntitySet != null) {
final UriResourceEntitySetImpl entitySetResource = new UriResourceEntitySetImpl(edmEntitySet);
if (tokenizer.next(TokenKind.OPEN)) {
final List<UriParameter> keyPredicates =
ParserHelper.parseKeyPredicate(tokenizer, entitySetResource.getEntityType(), null, edm, null, aliases);
entitySetResource.setKeyPredicates(keyPredicates);
}
ParserHelper.requireTokenEnd(tokenizer);
return entitySetResource;
}
final EdmSingleton edmSingleton = edmEntityContainer.getSingleton(oDataIdentifier);
if (edmSingleton != null) {
ParserHelper.requireTokenEnd(tokenizer);
return new UriResourceSingletonImpl(edmSingleton);
}
final EdmActionImport edmActionImport = edmEntityContainer.getActionImport(oDataIdentifier);
if (edmActionImport != null) {
ParserHelper.requireTokenEnd(tokenizer);
return new UriResourceActionImpl(edmActionImport);
}
final EdmFunctionImport edmFunctionImport = edmEntityContainer.getFunctionImport(oDataIdentifier);
if (edmFunctionImport != null) {
return functionCall(edmFunctionImport, null, null, false);
}
if (tokenizer.next(TokenKind.OPEN) || tokenizer.next(TokenKind.EOF)) {
throw new UriParserSemanticException("Unexpected start of resource-path segment.",
UriParserSemanticException.MessageKeys.RESOURCE_NOT_FOUND, oDataIdentifier);
} else {
throw new UriParserSyntaxException("Unexpected start of resource-path segment.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
private UriResource navigationOrProperty(final UriResource previous)
throws UriParserException, UriValidationException {
final String name = tokenizer.getText();
UriResourcePartTyped previousTyped = null;
EdmStructuredType structType = null;
requireTyped(previous, name);
if (((UriResourcePartTyped) previous).getType() instanceof EdmStructuredType) {
previousTyped = (UriResourcePartTyped) previous;
final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped);
structType = (EdmStructuredType) (previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter);
} else {
throw new UriParserSemanticException(
"Cannot parse '" + name + "'; previous path segment is not a structural type.",
UriParserSemanticException.MessageKeys.RESOURCE_PART_MUST_BE_PRECEDED_BY_STRUCTURAL_TYPE, name);
}
if (previousTyped.isCollection()) {
throw new UriParserSemanticException("Property '" + name + "' is not allowed after collection.",
UriParserSemanticException.MessageKeys.PROPERTY_AFTER_COLLECTION, name);
}
final EdmProperty property = structType.getStructuralProperty(name);
if (property != null) {
return property.isPrimitive()
|| property.getType().getKind() == EdmTypeKind.ENUM
|| property.getType().getKind() == EdmTypeKind.DEFINITION ?
new UriResourcePrimitivePropertyImpl(property) :
new UriResourceComplexPropertyImpl(property);
}
final EdmNavigationProperty navigationProperty = structType.getNavigationProperty(name);
if (navigationProperty == null) {
throw new UriParserSemanticException("Property '" + name + "' not found in type '"
+ structType.getFullQualifiedName().getFullQualifiedNameAsString() + "'",
UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE,
structType.getFullQualifiedName().getFullQualifiedNameAsString(), name);
}
List<UriParameter> keyPredicate =
ParserHelper.parseNavigationKeyPredicate(tokenizer, navigationProperty, edm, null, aliases);
ParserHelper.requireTokenEnd(tokenizer);
return new UriResourceNavigationPropertyImpl(navigationProperty)
.setKeyPredicates(keyPredicate);
}
private UriResource boundOperationOrTypeCast(UriResource previous)
throws UriParserException, UriValidationException {
final FullQualifiedName name = new FullQualifiedName(tokenizer.getText());
requireTyped(previous, name.getFullQualifiedNameAsString());
final UriResourcePartTyped previousTyped = (UriResourcePartTyped) previous;
final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped);
final EdmType previousType = previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter;
// We check for bound actions first because they cannot be followed by anything.
final EdmAction boundAction =
edm.getBoundAction(name, previousType.getFullQualifiedName(), previousTyped.isCollection());
if (boundAction != null) {
ParserHelper.requireTokenEnd(tokenizer);
return new UriResourceActionImpl(boundAction);
}
// Type casts can be syntactically indistinguishable from bound function calls in the case of additional keys.
// But normally they are shorter, so they come next.
final EdmStructuredType type = previousTyped.getType() instanceof EdmEntityType ?
edm.getEntityType(name) :
edm.getComplexType(name);
if (type != null) {
return typeCast(name, type, previousTyped);
}
if (tokenizer.next(TokenKind.EOF)) {
throw new UriParserSemanticException("Type '" + name.getFullQualifiedNameAsString() + "' not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name.getFullQualifiedNameAsString());
}
// Now a bound function call is the only remaining option.
return functionCall(null, name, previousType.getFullQualifiedName(), previousTyped.isCollection());
}
private void requireTyped(final UriResource previous, final String forWhat) throws UriParserException {
if (!(previous instanceof UriResourcePartTyped)) {
throw new UriParserSemanticException("Path segment before '" + forWhat + "' is not typed.",
UriParserSemanticException.MessageKeys.PREVIOUS_PART_NOT_TYPED, forWhat);
}
}
private UriResource typeCast(final FullQualifiedName name, final EdmStructuredType type,
final UriResourcePartTyped previousTyped) throws UriParserException, UriValidationException {
if (type.compatibleTo(previousTyped.getType())) {
EdmType previousTypeFilter = null;
if (previousTyped instanceof UriResourceWithKeysImpl) {
if (previousTyped.isCollection()) {
previousTypeFilter = ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnCollection();
if (previousTypeFilter != null) {
throw new UriParserSemanticException("Type filters are not chainable.",
UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE,
previousTypeFilter.getName(), type.getName());
}
((UriResourceWithKeysImpl) previousTyped).setCollectionTypeFilter(type);
} else {
previousTypeFilter = ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry();
if (previousTypeFilter != null) {
throw new UriParserSemanticException("Type filters are not chainable.",
UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE,
previousTypeFilter.getName(), type.getName());
}
((UriResourceWithKeysImpl) previousTyped).setEntryTypeFilter(type);
}
if (tokenizer.next(TokenKind.OPEN)) {
final List<UriParameter> keys =
ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) type, null, edm, null, aliases);
if (previousTyped.isCollection()) {
((UriResourceWithKeysImpl) previousTyped).setKeyPredicates(keys);
} else {
throw new UriParserSemanticException("Key not allowed here.",
UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED);
}
}
} else {
previousTypeFilter = ((UriResourceTypedImpl) previousTyped).getTypeFilter();
if (previousTypeFilter != null) {
throw new UriParserSemanticException("Type filters are not chainable.",
UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE,
previousTypeFilter.getName(), type.getName());
}
((UriResourceTypedImpl) previousTyped).setTypeFilter(type);
}
ParserHelper.requireTokenEnd(tokenizer);
return null;
} else {
throw new UriParserSemanticException(
"Type filter not compatible to previous path segment: " + name.getFullQualifiedNameAsString(),
UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, name.getFullQualifiedNameAsString());
}
}
private EdmType getPreviousTypeFilter(final UriResourcePartTyped previousTyped) {
if (previousTyped instanceof UriResourceWithKeysImpl) {
return ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry() == null ?
((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnCollection() :
((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry();
} else {
return ((UriResourceTypedImpl) previousTyped).getTypeFilter();
}
}
private UriResource functionCall(final EdmFunctionImport edmFunctionImport,
final FullQualifiedName boundFunctionName, final FullQualifiedName bindingParameterTypeName,
final boolean isBindingParameterCollection) throws UriParserException, UriValidationException {
final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, null, false, aliases);
final List<String> names = ParserHelper.getParameterNames(parameters);
EdmFunction function = null;
if (edmFunctionImport != null) {
function = edmFunctionImport.getUnboundFunction(names);
if (function == null) {
throw new UriParserSemanticException(
"Function of function import '" + edmFunctionImport.getName() + "' "
+ "with parameters " + names.toString() + " not found.",
UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, edmFunctionImport.getName(), names.toString());
}
} else {
function = edm.getBoundFunction(boundFunctionName,
bindingParameterTypeName, isBindingParameterCollection, names);
if (function == null) {
throw new UriParserSemanticException(
"Function " + boundFunctionName + " not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, boundFunctionName.getFullQualifiedNameAsString());
}
}
ParserHelper.validateFunctionParameters(function, parameters, edm, null, aliases);
ParserHelper.validateFunctionParameterFacets(function, parameters, edm, aliases);
UriResourceFunctionImpl resource = new UriResourceFunctionImpl(edmFunctionImport, function, parameters);
if (tokenizer.next(TokenKind.OPEN)) {
if (function.getReturnType() != null
&& function.getReturnType().getType().getKind() == EdmTypeKind.ENTITY
&& function.getReturnType().isCollection()) {
resource.setKeyPredicates(
ParserHelper.parseKeyPredicate(tokenizer,
(EdmEntityType) function.getReturnType().getType(), null, edm, null, aliases));
} else {
throw new UriParserSemanticException("A key is not allowed.",
UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED);
}
}
ParserHelper.requireTokenEnd(tokenizer);
return resource;
}
}