blob: 49d7638bf32785d5a9202873fa69abe2ee6b1d5e [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 org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmAction;
import org.apache.olingo.commons.api.edm.EdmComplexType;
import org.apache.olingo.commons.api.edm.EdmFunction;
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.FullQualifiedName;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.queryoption.SelectItem;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.UriResourceActionImpl;
import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
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.parser.UriTokenizer.TokenKind;
import org.apache.olingo.server.core.uri.queryoption.SelectItemImpl;
import org.apache.olingo.server.core.uri.queryoption.SelectOptionImpl;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
public class SelectParser {
private final Edm edm;
public SelectParser(final Edm edm) {
this.edm = edm;
}
public SelectOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType,
final boolean referencedIsCollection) throws UriParserException, UriValidationException {
List<SelectItem> selectItems = new ArrayList<>();
SelectItem item;
do {
item = parseItem(tokenizer, referencedType, referencedIsCollection);
selectItems.add(item);
} while (tokenizer.next(TokenKind.COMMA));
return new SelectOptionImpl().setSelectItems(selectItems);
}
private SelectItem parseItem(UriTokenizer tokenizer,
final EdmStructuredType referencedType, final boolean referencedIsCollection) throws UriParserException {
SelectItemImpl item = new SelectItemImpl();
if (tokenizer.next(TokenKind.STAR)) {
item.setStar(true);
} else if (tokenizer.next(TokenKind.QualifiedName)) {
// The namespace or its alias could consist of dot-separated OData identifiers.
final FullQualifiedName allOperationsInSchema = parseAllOperationsInSchema(tokenizer);
if (allOperationsInSchema != null) {
item.addAllOperationsInSchema(allOperationsInSchema);
} else {
ensureReferencedTypeNotNull(referencedType);
final FullQualifiedName qualifiedName = new FullQualifiedName(tokenizer.getText());
EdmStructuredType type = edm.getEntityType(qualifiedName);
if (type == null) {
type = edm.getComplexType(qualifiedName);
}
if (type == null) {
item.setResourcePath(new UriInfoImpl().setKind(UriInfoKind.resource).addResourcePart(
parseBoundOperation(tokenizer, qualifiedName, referencedType, referencedIsCollection)));
} else {
if (type.compatibleTo(referencedType)) {
item.setTypeFilter(type);
if (tokenizer.next(TokenKind.SLASH)) {
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
addSelectPath(tokenizer, type, resource);
item.setResourcePath(resource);
}
} else {
throw new UriParserSemanticException("The type cast is not compatible.",
UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, type.getName());
}
}
}
} else {
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
// The namespace or its alias could be a single OData identifier.
final FullQualifiedName allOperationsInSchema = parseAllOperationsInSchema(tokenizer);
if (allOperationsInSchema != null) {
item.addAllOperationsInSchema(allOperationsInSchema);
} else {
ensureReferencedTypeNotNull(referencedType);
UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource);
addSelectPath(tokenizer, referencedType, resource);
item.setResourcePath(resource);
}
}
return item;
}
private FullQualifiedName parseAllOperationsInSchema(UriTokenizer tokenizer) throws UriParserException {
final String namespace = tokenizer.getText();
if (tokenizer.next(TokenKind.DOT)) {
if (tokenizer.next(TokenKind.STAR)) {
// Validate the namespace. Currently a namespace from a non-default schema is not supported.
// There is no direct access to the namespace without loading the whole schema;
// however, the default entity container should always be there, so its access methods can be used.
if (edm.getEntityContainer(new FullQualifiedName(namespace, edm.getEntityContainer().getName())) == null) {
throw new UriParserSemanticException("Wrong namespace '" + namespace + "'.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, namespace);
}
return new FullQualifiedName(namespace, tokenizer.getText());
} else {
throw new UriParserSemanticException("Expected star after dot.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, "");
}
}
return null;
}
private void ensureReferencedTypeNotNull(final EdmStructuredType referencedType) throws UriParserException {
if (referencedType == null) {
throw new UriParserSemanticException("The referenced part is not typed.",
UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS, "select");
}
}
private UriResourcePartTyped parseBoundOperation(UriTokenizer tokenizer, final FullQualifiedName qualifiedName,
final EdmStructuredType referencedType, final boolean referencedIsCollection) throws UriParserException {
final EdmAction boundAction = edm.getBoundAction(qualifiedName,
referencedType.getFullQualifiedName(),
referencedIsCollection);
if (boundAction == null) {
final List<String> parameterNames = parseFunctionParameterNames(tokenizer);
final EdmFunction boundFunction = edm.getBoundFunction(qualifiedName,
referencedType.getFullQualifiedName(), referencedIsCollection, parameterNames);
if (boundFunction == null) {
throw new UriParserSemanticException("Function not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, qualifiedName.getFullQualifiedNameAsString());
} else {
return new UriResourceFunctionImpl(null, boundFunction, null);
}
} else {
return new UriResourceActionImpl(boundAction);
}
}
private List<String> parseFunctionParameterNames(UriTokenizer tokenizer) throws UriParserException {
List<String> names = new ArrayList<>();
if (tokenizer.next(TokenKind.OPEN)) {
do {
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
names.add(tokenizer.getText());
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
}
return names;
}
private void addSelectPath(UriTokenizer tokenizer, final EdmStructuredType referencedType, UriInfoImpl resource)
throws UriParserException {
final String name = tokenizer.getText();
final EdmProperty property = referencedType.getStructuralProperty(name);
if (property == null) {
final EdmNavigationProperty navigationProperty = referencedType.getNavigationProperty(name);
if (navigationProperty == null) {
throw new UriParserSemanticException("Selected property not found.",
UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE,
referencedType.getName(), name);
} else {
resource.addResourcePart(new UriResourceNavigationPropertyImpl(navigationProperty));
}
} else if (property.isPrimitive()
|| property.getType().getKind() == EdmTypeKind.ENUM
|| property.getType().getKind() == EdmTypeKind.DEFINITION) {
resource.addResourcePart(new UriResourcePrimitivePropertyImpl(property));
} else {
UriResourceComplexPropertyImpl complexPart = new UriResourceComplexPropertyImpl(property);
resource.addResourcePart(complexPart);
if (tokenizer.next(TokenKind.SLASH)) {
if (tokenizer.next(TokenKind.QualifiedName)) {
final FullQualifiedName qualifiedName = new FullQualifiedName(tokenizer.getText());
final EdmComplexType type = edm.getComplexType(qualifiedName);
if (type == null) {
throw new UriParserSemanticException("Type not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, qualifiedName.getFullQualifiedNameAsString());
} else if (type.compatibleTo(property.getType())) {
complexPart.setTypeFilter(type);
if (tokenizer.next(TokenKind.SLASH)) {
if (tokenizer.next(TokenKind.ODataIdentifier)) {
addSelectPath(tokenizer, type, resource);
} else {
throw new UriParserSemanticException("Unknown part after '/'.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, "");
}
}
} else {
throw new UriParserSemanticException("The type cast is not compatible.",
UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, type.getName());
}
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
addSelectPath(tokenizer, (EdmStructuredType) property.getType(), resource);
} else if (tokenizer.next(TokenKind.SLASH)) {
throw new UriParserSyntaxException("Illegal $select expression.",
UriParserSyntaxException.MessageKeys.SYNTAX);
} else {
throw new UriParserSemanticException("Unknown part after '/'.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, "");
}
}
}
}
}