| /* |
| * 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, ""); |
| } |
| } |
| } |
| } |
| } |