| /******************************************************************************* |
| * 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.odata2.core.uri; |
| |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.olingo.odata2.api.commons.InlineCount; |
| import org.apache.olingo.odata2.api.edm.Edm; |
| import org.apache.olingo.odata2.api.edm.EdmComplexType; |
| import org.apache.olingo.odata2.api.edm.EdmEntityContainer; |
| import org.apache.olingo.odata2.api.edm.EdmEntitySet; |
| import org.apache.olingo.odata2.api.edm.EdmEntityType; |
| import org.apache.olingo.odata2.api.edm.EdmException; |
| import org.apache.olingo.odata2.api.edm.EdmFacets; |
| import org.apache.olingo.odata2.api.edm.EdmFunctionImport; |
| import org.apache.olingo.odata2.api.edm.EdmLiteral; |
| import org.apache.olingo.odata2.api.edm.EdmLiteralException; |
| import org.apache.olingo.odata2.api.edm.EdmMultiplicity; |
| import org.apache.olingo.odata2.api.edm.EdmNavigationProperty; |
| import org.apache.olingo.odata2.api.edm.EdmParameter; |
| import org.apache.olingo.odata2.api.edm.EdmProperty; |
| import org.apache.olingo.odata2.api.edm.EdmSimpleType; |
| import org.apache.olingo.odata2.api.edm.EdmSimpleTypeFacade; |
| import org.apache.olingo.odata2.api.edm.EdmType; |
| import org.apache.olingo.odata2.api.edm.EdmTypeKind; |
| import org.apache.olingo.odata2.api.edm.EdmTyped; |
| import org.apache.olingo.odata2.api.exception.MessageReference; |
| import org.apache.olingo.odata2.api.exception.ODataBadRequestException; |
| import org.apache.olingo.odata2.api.exception.ODataException; |
| import org.apache.olingo.odata2.api.exception.ODataMessageException; |
| import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode; |
| import org.apache.olingo.odata2.api.uri.KeyPredicate; |
| import org.apache.olingo.odata2.api.uri.NavigationPropertySegment; |
| import org.apache.olingo.odata2.api.uri.PathSegment; |
| import org.apache.olingo.odata2.api.uri.SelectItem; |
| import org.apache.olingo.odata2.api.uri.UriInfo; |
| import org.apache.olingo.odata2.api.uri.UriNotMatchingException; |
| import org.apache.olingo.odata2.api.uri.UriParser; |
| import org.apache.olingo.odata2.api.uri.UriSyntaxException; |
| import org.apache.olingo.odata2.api.uri.expression.ExpressionParserException; |
| import org.apache.olingo.odata2.api.uri.expression.FilterExpression; |
| import org.apache.olingo.odata2.api.uri.expression.OrderByExpression; |
| import org.apache.olingo.odata2.core.ODataPathSegmentImpl; |
| import org.apache.olingo.odata2.core.commons.Decoder; |
| import org.apache.olingo.odata2.core.edm.EdmSimpleTypeFacadeImpl; |
| import org.apache.olingo.odata2.core.exception.ODataRuntimeException; |
| import org.apache.olingo.odata2.core.uri.expression.FilterParserImpl; |
| import org.apache.olingo.odata2.core.uri.expression.OrderByParserImpl; |
| |
| /** |
| * Parser for the OData part of the URL. |
| * |
| */ |
| public class UriParserImpl extends UriParser { |
| |
| private static final Pattern INITIAL_SEGMENT_PATTERN = Pattern |
| .compile("(?:([^.()]+)\\.)?([^.()]+)(?:\\((.+)\\)|(\\(\\)))?"); |
| private static final Pattern NAVIGATION_SEGMENT_PATTERN = Pattern.compile("([^()]+)(?:\\((.+)\\)|(\\(\\)))?"); |
| private static final Pattern NAMED_VALUE_PATTERN = Pattern.compile("(?:([^=]+)=)?([^=]+)"); |
| private static final char COMMA = ','; |
| private static final char SQUOTE = '\''; |
| private static final String ACCEPT_FORM_ENCODING = "odata-accept-forms-encoding"; |
| |
| private final Edm edm; |
| private final EdmSimpleTypeFacade simpleTypeFacade; |
| private List<String> pathSegments; |
| private String currentPathSegment; |
| private UriInfoImpl uriResult; |
| private Map<SystemQueryOption, String> systemQueryOptions; |
| private Map<String, String> otherQueryParameters; |
| |
| public UriParserImpl(final Edm edm) { |
| this.edm = edm; |
| simpleTypeFacade = new EdmSimpleTypeFacadeImpl(); |
| } |
| |
| /** |
| * Parse the URI part after an OData service root, |
| * already splitted into path segments and query parameters. |
| * @param pathSegments the {@link PathSegment}s of the resource path, |
| * potentially percent-encoded |
| * @param queryParameters the query parameters, already percent-decoded |
| * @return a {@link UriInfoImpl} instance containing the parsed information |
| */ |
| @Override |
| public UriInfo parse(final List<PathSegment> pathSegments, final Map<String, String> queryParameters) |
| throws UriSyntaxException, UriNotMatchingException, EdmException { |
| |
| return parseAll(pathSegments, convertFromSingleMapToMultiMap(queryParameters)); |
| } |
| |
| @Override |
| public UriInfo parseAll(final List<PathSegment> pathSegments, final Map<String, List<String>> allQueryParameters) |
| throws UriSyntaxException, UriNotMatchingException, EdmException { |
| |
| this.pathSegments = copyPathSegmentList(pathSegments); |
| systemQueryOptions = new HashMap<SystemQueryOption, String>(); |
| otherQueryParameters = new HashMap<String, String>(); |
| uriResult = new UriInfoImpl(); |
| |
| preparePathSegments(); |
| |
| handleResourcePath(); |
| |
| distributeQueryParameters(allQueryParameters); |
| checkSystemQueryOptionsCompatibility(); |
| handleSystemQueryOptions(); |
| handleOtherQueryParameters(); |
| |
| return uriResult; |
| } |
| |
| private <T, K> Map<T, List<K>> convertFromSingleMapToMultiMap(final Map<T, K> singleMap) { |
| Map<T, List<K>> multiMap = new HashMap<T, List<K>>(); |
| |
| for (Entry<T, K> entry : singleMap.entrySet()) { |
| List<K> valueList = new LinkedList<K>(); |
| valueList.add(entry.getValue()); |
| |
| multiMap.put(entry.getKey(), valueList); |
| } |
| |
| return multiMap; |
| } |
| |
| private void preparePathSegments() throws UriSyntaxException { |
| // Remove an empty path segment at the start of the OData part of the resource path. |
| if (!pathSegments.isEmpty() && "".equals(pathSegments.get(0))) { |
| pathSegments.remove(0); |
| } |
| |
| // Remove an empty path segment at the end of the resource path, |
| // although there is nothing in the OData specification that would allow that. |
| if (!pathSegments.isEmpty() && "".equals(pathSegments.get(pathSegments.size() - 1))) { |
| pathSegments.remove(pathSegments.size() - 1); |
| } |
| |
| // Intermediate empty path segments are an error, however. |
| for (String pathSegment : pathSegments) { |
| if ("".equals(pathSegment)) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| } |
| } |
| |
| private void handleResourcePath() throws UriSyntaxException, UriNotMatchingException, EdmException { |
| if (pathSegments.isEmpty()) { |
| uriResult.setUriType(UriType.URI0); |
| } else { |
| |
| currentPathSegment = pathSegments.remove(0); |
| final String decodedPath = percentDecode(currentPathSegment); |
| |
| if ("$metadata".equals(decodedPath)) { |
| ensureLastSegment(); |
| uriResult.setUriType(UriType.URI8); |
| |
| } else if ("$batch".equals(decodedPath)) { |
| ensureLastSegment(); |
| uriResult.setUriType(UriType.URI9); |
| |
| } else { |
| handleNormalInitialSegment(); |
| } |
| } |
| } |
| |
| private void handleNormalInitialSegment() throws UriSyntaxException, UriNotMatchingException, EdmException { |
| final Matcher matcher = INITIAL_SEGMENT_PATTERN.matcher(currentPathSegment); |
| if (!matcher.matches()) { |
| throw new UriNotMatchingException(UriNotMatchingException.MATCHPROBLEM.addContent(currentPathSegment)); |
| } |
| |
| final String entityContainerName = percentDecode(matcher.group(1)); |
| final String segmentName = percentDecode(matcher.group(2)); |
| final String keyPredicate = matcher.group(3); |
| final String emptyParentheses = matcher.group(4); |
| |
| final EdmEntityContainer entityContainer = |
| entityContainerName == null ? edm.getDefaultEntityContainer() : edm.getEntityContainer(entityContainerName); |
| if (entityContainer == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.CONTAINERNOTFOUND.addContent(entityContainerName)); |
| } |
| uriResult.setEntityContainer(entityContainer); |
| |
| final EdmEntitySet entitySet = entityContainer.getEntitySet(segmentName); |
| if (entitySet != null) { |
| uriResult.setStartEntitySet(entitySet); |
| handleEntitySet(entitySet, keyPredicate); |
| } else { |
| final EdmFunctionImport functionImport = entityContainer.getFunctionImport(segmentName); |
| if (functionImport == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.NOTFOUND.addContent(segmentName)); |
| } |
| uriResult.setFunctionImport(functionImport); |
| handleFunctionImport(functionImport, emptyParentheses, keyPredicate); |
| } |
| } |
| |
| private void handleEntitySet(final EdmEntitySet entitySet, final String keyPredicate) throws UriSyntaxException, |
| UriNotMatchingException, EdmException { |
| final EdmEntityType entityType = entitySet.getEntityType(); |
| |
| uriResult.setTargetType(entityType); |
| uriResult.setTargetEntitySet(entitySet); |
| |
| if (keyPredicate == null) { |
| if (pathSegments.isEmpty()) { |
| uriResult.setUriType(UriType.URI1); |
| } else { |
| currentPathSegment = pathSegments.remove(0); |
| checkCount(); |
| if (uriResult.isCount()) { |
| uriResult.setUriType(UriType.URI15); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.ENTITYSETINSTEADOFENTITY.addContent(entitySet.getName())); |
| } |
| } |
| } else { |
| uriResult.setKeyPredicates(parseKey(keyPredicate, entityType)); |
| if (pathSegments.isEmpty()) { |
| uriResult.setUriType(UriType.URI2); |
| } else { |
| handleNavigationPathOptions(); |
| } |
| } |
| } |
| |
| private void handleNavigationPathOptions() throws UriSyntaxException, UriNotMatchingException, EdmException { |
| currentPathSegment = pathSegments.remove(0); |
| final String decodedPath = percentDecode(currentPathSegment); |
| |
| checkCount(); |
| if (uriResult.isCount()) { |
| uriResult.setUriType(UriType.URI16); // Count of multiple entities is handled elsewhere |
| |
| } else if ("$value".equals(decodedPath)) { |
| if (uriResult.getTargetEntitySet().getEntityType().hasStream()) { |
| ensureLastSegment(); |
| uriResult.setUriType(UriType.URI17); |
| uriResult.setValue(true); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.NOMEDIARESOURCE); |
| } |
| |
| } else if ("$links".equals(decodedPath)) { |
| uriResult.setLinks(true); |
| if (pathSegments.isEmpty()) { |
| throw new UriSyntaxException(UriSyntaxException.MUSTNOTBELASTSEGMENT.addContent(currentPathSegment)); |
| } |
| currentPathSegment = pathSegments.remove(0); |
| handleNavigationProperties(); |
| |
| } else { |
| handleNavigationProperties(); |
| } |
| } |
| |
| private void handleNavigationProperties() throws UriSyntaxException, UriNotMatchingException, EdmException { |
| |
| final Matcher matcher = NAVIGATION_SEGMENT_PATTERN.matcher(currentPathSegment); |
| if (!matcher.matches()) { |
| throw new UriNotMatchingException(UriNotMatchingException.MATCHPROBLEM.addContent(currentPathSegment)); |
| } |
| |
| final String navigationPropertyName = percentDecode(matcher.group(1)); |
| final String keyPredicateName = matcher.group(2); |
| final String emptyParentheses = matcher.group(3); |
| |
| final EdmTyped property = uriResult.getTargetEntitySet().getEntityType().getProperty(navigationPropertyName); |
| if (property == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.PROPERTYNOTFOUND.addContent(navigationPropertyName)); |
| } |
| |
| switch (property.getType().getKind()) { |
| case SIMPLE: |
| case COMPLEX: |
| if (keyPredicateName != null || emptyParentheses != null) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(currentPathSegment)); |
| } |
| if (uriResult.isLinks()) { |
| throw new UriSyntaxException(UriSyntaxException.NONAVIGATIONPROPERTY.addContent(property)); |
| } |
| |
| handlePropertyPath((EdmProperty) property); |
| break; |
| |
| case ENTITY: // navigation properties point to entities |
| final EdmNavigationProperty navigationProperty = (EdmNavigationProperty) property; |
| if (keyPredicateName != null || emptyParentheses != null) { |
| if (navigationProperty.getMultiplicity() != EdmMultiplicity.MANY) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(currentPathSegment)); |
| } |
| } |
| |
| addNavigationSegment(keyPredicateName, navigationProperty); |
| |
| boolean many = false; |
| if (navigationProperty.getMultiplicity() == EdmMultiplicity.MANY) { |
| many = keyPredicateName == null; |
| } |
| |
| if (pathSegments.isEmpty()) { |
| if (many) { |
| if (uriResult.isLinks()) { |
| uriResult.setUriType(UriType.URI7B); |
| } else { |
| uriResult.setUriType(UriType.URI6B); |
| } |
| } else if (uriResult.isLinks()) { |
| uriResult.setUriType(UriType.URI7A); |
| } else { |
| uriResult.setUriType(UriType.URI6A); |
| } |
| } else if (many || uriResult.isLinks()) { |
| currentPathSegment = pathSegments.remove(0); |
| checkCount(); |
| if (!uriResult.isCount()) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(currentPathSegment)); |
| } |
| if (many) { |
| if (uriResult.isLinks()) { |
| uriResult.setUriType(UriType.URI50B); |
| } else { |
| uriResult.setUriType(UriType.URI15); |
| } |
| } else { |
| uriResult.setUriType(UriType.URI50A); |
| } |
| } else { |
| handleNavigationPathOptions(); |
| } |
| break; |
| |
| default: |
| throw new UriSyntaxException(UriSyntaxException.INVALIDPROPERTYTYPE.addContent(property.getType().getKind())); |
| } |
| } |
| |
| private void addNavigationSegment(final String keyPredicateName, final EdmNavigationProperty navigationProperty) |
| throws UriSyntaxException, EdmException { |
| final EdmEntitySet targetEntitySet = uriResult.getTargetEntitySet().getRelatedEntitySet(navigationProperty); |
| final EdmEntityType targetEntityType = targetEntitySet.getEntityType(); |
| uriResult.setTargetEntitySet(targetEntitySet); |
| uriResult.setTargetType(targetEntityType); |
| |
| NavigationSegmentImpl navigationSegment = new NavigationSegmentImpl(); |
| navigationSegment.setEntitySet(targetEntitySet); |
| navigationSegment.setNavigationProperty(navigationProperty); |
| if (keyPredicateName != null) { |
| navigationSegment.setKeyPredicates(parseKey(keyPredicateName, targetEntityType)); |
| } |
| uriResult.addNavigationSegment(navigationSegment); |
| } |
| |
| private void handlePropertyPath(final EdmProperty property) throws UriSyntaxException, UriNotMatchingException, |
| EdmException { |
| uriResult.addProperty(property); |
| final EdmType type = property.getType(); |
| |
| if (pathSegments.isEmpty()) { |
| if (type.getKind() == EdmTypeKind.SIMPLE) { |
| if (uriResult.getPropertyPath().size() == 1) { |
| uriResult.setUriType(UriType.URI5); |
| } else { |
| uriResult.setUriType(UriType.URI4); |
| } |
| } else if (type.getKind() == EdmTypeKind.COMPLEX) { |
| uriResult.setUriType(UriType.URI3); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDPROPERTYTYPE.addContent(type.getKind())); |
| } |
| uriResult.setTargetType(type); |
| } else { |
| |
| currentPathSegment = percentDecode(pathSegments.remove(0)); |
| switch (type.getKind()) { |
| case SIMPLE: |
| if ("$value".equals(percentDecode(currentPathSegment))) { |
| ensureLastSegment(); |
| uriResult.setValue(true); |
| if (uriResult.getPropertyPath().size() == 1) { |
| uriResult.setUriType(UriType.URI5); |
| } else { |
| uriResult.setUriType(UriType.URI4); |
| } |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(currentPathSegment)); |
| } |
| uriResult.setTargetType(type); |
| break; |
| |
| case COMPLEX: |
| final EdmProperty nextProperty = (EdmProperty) ((EdmComplexType) type).getProperty(currentPathSegment); |
| if (nextProperty == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.PROPERTYNOTFOUND.addContent(currentPathSegment)); |
| } |
| |
| handlePropertyPath(nextProperty); |
| break; |
| |
| default: |
| throw new UriSyntaxException(UriSyntaxException.INVALIDPROPERTYTYPE.addContent(type.getKind())); |
| } |
| } |
| } |
| |
| private void ensureLastSegment() throws UriSyntaxException { |
| if (!pathSegments.isEmpty()) { |
| throw new UriSyntaxException(UriSyntaxException.MUSTBELASTSEGMENT.addContent(currentPathSegment)); |
| } |
| } |
| |
| private void checkCount() throws UriSyntaxException { |
| if ("$count".equals(percentDecode(currentPathSegment))) { |
| if (pathSegments.isEmpty()) { |
| uriResult.setCount(true); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.MUSTBELASTSEGMENT.addContent(currentPathSegment)); |
| } |
| } |
| } |
| |
| private ArrayList<KeyPredicate> parseKey(final String keyPredicate, final EdmEntityType entityType) |
| throws UriSyntaxException, EdmException { |
| final List<EdmProperty> keyProperties = entityType.getKeyProperties(); |
| ArrayList<EdmProperty> parsedKeyProperties = new ArrayList<EdmProperty>(); |
| ArrayList<KeyPredicate> keyPredicates = new ArrayList<KeyPredicate>(); |
| |
| final List<String> keys = splitKeyPredicate(keyPredicate); |
| for (final String key : keys) { |
| final Matcher matcher = NAMED_VALUE_PATTERN.matcher(key); |
| if (!matcher.matches()) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDKEYPREDICATE.addContent(keyPredicate)); |
| } |
| |
| String name = percentDecode(matcher.group(1)); |
| final String value = percentDecode(matcher.group(2)); |
| |
| if (name == null) { |
| if (keyProperties.size() == 1) { |
| name = keyProperties.get(0).getName(); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.MISSINGKEYPREDICATENAME.addContent(key)); |
| } |
| } |
| |
| EdmProperty keyProperty = null; |
| for (final EdmProperty testKeyProperty : keyProperties) { |
| if (testKeyProperty.getName().equals(name)) { |
| keyProperty = testKeyProperty; |
| break; |
| } |
| } |
| if (keyProperty == null) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDKEYPREDICATE.addContent(keyPredicate)); |
| } |
| if (parsedKeyProperties.contains(keyProperty)) { |
| throw new UriSyntaxException(UriSyntaxException.DUPLICATEKEYNAMES.addContent(keyPredicate)); |
| } |
| parsedKeyProperties.add(keyProperty); |
| |
| final EdmLiteral uriLiteral = parseLiteral(value, (EdmSimpleType) keyProperty.getType()); |
| keyPredicates.add(new KeyPredicateImpl(uriLiteral.getLiteral(), keyProperty)); |
| } |
| |
| if (parsedKeyProperties.size() != keyProperties.size()) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDKEYPREDICATE.addContent(keyPredicate)); |
| } |
| |
| return keyPredicates; |
| } |
| |
| /** |
| * Split the <code>keyPredicate</code> string into separate keys (named keys). |
| * e.g. <b>EmployeeId='1,,,2',Test='as'</b> will result in a list with two elements |
| * <b>EmployeeId='1,,,2'</b> and <b>Test='as'</b>. |
| * |
| * e.g. <b>'42'</b> will result in a list with onw element <b>'42'</b>. |
| * |
| * Snippets from ABNF (odata-abnf-construction-rules) |
| * |
| * <code> |
| * keyPredicate = simpleKey / compoundKey |
| * simpleKey = OPEN keyPropertyValue CLOSE |
| * compoundKey = OPEN keyValuePair *( COMMA keyValuePair ) CLOSE |
| * keyValuePair = ( primitiveKeyProperty / keyPropertyAlias ) EQ keyPropertyValue |
| * keyPropertyValue = primitiveLiteral |
| * keyPropertyAlias = odataIdentifier |
| * </code> |
| * |
| * <code> |
| * string = SQUOTE *( SQUOTE-in-string / pchar-no-SQUOTE ) SQUOTE |
| * SQUOTE-in-string = SQUOTE SQUOTE ; two consecutive single quotes represent one within a string literal |
| * </code> |
| * |
| * @param keyPredicate keyPredicate to split |
| * @return list of separate (named) key values |
| */ |
| private List<String> splitKeyPredicate(String keyPredicate) { |
| StringBuilder b = new StringBuilder(); |
| final List<String> keys = new ArrayList<String>(); |
| boolean inStringKeyValue = false; |
| for (int i = 0; i < keyPredicate.length(); i++) { |
| final char curChar = keyPredicate.charAt(i); |
| if (SQUOTE == curChar) { |
| // also works with SQUOTE-in-string |
| inStringKeyValue = !inStringKeyValue; |
| b.append(curChar); |
| } else if (COMMA == curChar && !inStringKeyValue) { |
| keys.add(b.toString()); |
| b = new StringBuilder(); |
| } else { |
| b.append(curChar); |
| } |
| } |
| keys.add(b.toString()); |
| |
| return keys; |
| } |
| |
| private void handleFunctionImport(final EdmFunctionImport functionImport, final String emptyParentheses, |
| final String keyPredicate) throws UriSyntaxException, UriNotMatchingException, EdmException { |
| final EdmTyped returnType = functionImport.getReturnType(); |
| |
| if (returnType != null && returnType.getType() != null) { |
| final EdmType type = returnType.getType(); |
| final boolean isCollection = returnType.getMultiplicity() == EdmMultiplicity.MANY; |
| |
| if (type.getKind() == EdmTypeKind.ENTITY && isCollection) { |
| handleEntitySet(functionImport.getEntitySet(), keyPredicate); |
| return; |
| } |
| |
| if (emptyParentheses != null) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(emptyParentheses)); |
| } |
| |
| uriResult.setTargetType(type); |
| switch (type.getKind()) { |
| case SIMPLE: |
| uriResult.setUriType(isCollection ? UriType.URI13 : UriType.URI14); |
| break; |
| case COMPLEX: |
| uriResult.setUriType(isCollection ? UriType.URI11 : UriType.URI12); |
| break; |
| case ENTITY: |
| uriResult.setUriType(UriType.URI10); |
| break; |
| default: |
| throw new UriSyntaxException(UriSyntaxException.INVALIDRETURNTYPE.addContent(type.getKind())); |
| } |
| |
| if (!pathSegments.isEmpty()) { |
| if (uriResult.getUriType() == UriType.URI14) { |
| currentPathSegment = pathSegments.remove(0); |
| if ("$value".equals(percentDecode(currentPathSegment))) { |
| uriResult.setValue(true); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(currentPathSegment)); |
| } |
| } |
| } |
| } else { |
| uriResult.setUriType(UriType.URI14); |
| } |
| ensureLastSegment(); |
| } |
| |
| private void distributeQueryParameters(final Map<String, List<String>> queryParameters) throws UriSyntaxException { |
| boolean formEncoding = false; |
| if(queryParameters.containsKey(ACCEPT_FORM_ENCODING)){ |
| formEncoding=Boolean.parseBoolean(queryParameters.get(ACCEPT_FORM_ENCODING).get(0)); |
| queryParameters.remove(ACCEPT_FORM_ENCODING); |
| } |
| for (final String queryOptionString : queryParameters.keySet()) { |
| final String decodedString = percentDecode(queryOptionString); |
| final List<String> valueList = queryParameters.get(queryOptionString); |
| |
| if (valueList.size() >= 1) { |
| String value = valueList.get(0); |
| if(formEncoding){ |
| value = getFormEncodedValue(value); |
| } |
| if (decodedString.startsWith("$")) { |
| SystemQueryOption queryOption; |
| try { |
| queryOption = SystemQueryOption.valueOf(decodedString); |
| } catch (IllegalArgumentException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSYSTEMQUERYOPTION.addContent(queryOptionString), e); |
| } |
| if ("".equals(value)) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDNULLVALUE.addContent(queryOptionString)); |
| } else { |
| if (valueList.size() == 1 && !systemQueryOptions.containsKey(queryOption)) { |
| systemQueryOptions.put(queryOption, value); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.DUPLICATESYSTEMQUERYPARAMETES |
| .addContent(queryOptionString)); |
| } |
| } |
| } else { |
| otherQueryParameters.put(decodedString, value); |
| } |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDNULLVALUE.addContent(queryOptionString)); |
| } |
| } |
| } |
| |
| private String getFormEncodedValue(String value) { |
| if(value.contains("+")){ |
| value = value.replaceAll("\\+", " "); |
| } |
| return value; |
| } |
| |
| private void checkSystemQueryOptionsCompatibility() throws UriSyntaxException { |
| final UriType uriType = uriResult.getUriType(); |
| |
| for (SystemQueryOption queryOption : systemQueryOptions.keySet()) { |
| |
| if (queryOption == SystemQueryOption.$format && (uriType == UriType.URI4 || uriType == UriType.URI5) |
| && uriResult.isValue()) { |
| throw new UriSyntaxException(UriSyntaxException.INCOMPATIBLESYSTEMQUERYOPTION.addContent(queryOption)); |
| } |
| |
| if (!uriType.isCompatible(queryOption)) { |
| throw new UriSyntaxException(UriSyntaxException.INCOMPATIBLESYSTEMQUERYOPTION.addContent(queryOption)); |
| } |
| } |
| } |
| |
| private void handleSystemQueryOptions() throws UriSyntaxException, UriNotMatchingException, EdmException { |
| |
| for (SystemQueryOption queryOption : systemQueryOptions.keySet()) { |
| switch (queryOption) { |
| case $format: |
| handleSystemQueryOptionFormat(systemQueryOptions.get(SystemQueryOption.$format)); |
| break; |
| case $filter: |
| handleSystemQueryOptionFilter(systemQueryOptions.get(SystemQueryOption.$filter)); |
| break; |
| case $inlinecount: |
| handleSystemQueryOptionInlineCount(systemQueryOptions.get(SystemQueryOption.$inlinecount)); |
| break; |
| case $orderby: |
| handleSystemQueryOptionOrderBy(systemQueryOptions.get(SystemQueryOption.$orderby)); |
| break; |
| case $skiptoken: |
| handleSystemQueryOptionSkipToken(systemQueryOptions.get(SystemQueryOption.$skiptoken)); |
| break; |
| case $skip: |
| handleSystemQueryOptionSkip(systemQueryOptions.get(SystemQueryOption.$skip)); |
| break; |
| case $top: |
| handleSystemQueryOptionTop(systemQueryOptions.get(SystemQueryOption.$top)); |
| break; |
| case $expand: |
| handleSystemQueryOptionExpand(systemQueryOptions.get(SystemQueryOption.$expand)); |
| break; |
| case $select: |
| handleSystemQueryOptionSelect(systemQueryOptions.get(SystemQueryOption.$select)); |
| break; |
| default: |
| throw new ODataRuntimeException("Invalid System Query Option " + queryOption); |
| } |
| } |
| } |
| |
| private void handleSystemQueryOptionFormat(final String format) throws UriSyntaxException { |
| uriResult.setFormat(format); |
| } |
| |
| private void handleSystemQueryOptionFilter(final String filter) throws UriSyntaxException { |
| final EdmType targetType = uriResult.getTargetType(); |
| if (targetType instanceof EdmEntityType) { |
| try { |
| uriResult.setFilter(new FilterParserImpl((EdmEntityType) targetType).parseFilterString(filter, true)); |
| } catch (ExpressionParserException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDFILTEREXPRESSION.addContent(filter), e); |
| } catch (ODataMessageException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDFILTEREXPRESSION.addContent(filter), e); |
| } |
| } |
| } |
| |
| private void handleSystemQueryOptionOrderBy(final String orderBy) throws UriSyntaxException { |
| final EdmType targetType = uriResult.getTargetType(); |
| if (targetType instanceof EdmEntityType) { |
| try { |
| uriResult.setOrderBy(parseOrderByString((EdmEntityType) targetType, orderBy)); |
| } catch (ExpressionParserException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDORDERBYEXPRESSION.addContent(orderBy), e); |
| } catch (ODataMessageException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDORDERBYEXPRESSION.addContent(orderBy), e); |
| } |
| } |
| } |
| |
| private void handleSystemQueryOptionInlineCount(final String inlineCount) throws UriSyntaxException { |
| if ("allpages".equals(inlineCount)) { |
| uriResult.setInlineCount(InlineCount.ALLPAGES); |
| } else if ("none".equals(inlineCount)) { |
| uriResult.setInlineCount(InlineCount.NONE); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDVALUE.addContent(inlineCount)); |
| } |
| } |
| |
| private void handleSystemQueryOptionSkipToken(final String skiptoken) throws UriSyntaxException { |
| uriResult.setSkipToken(skiptoken); |
| } |
| |
| private void handleSystemQueryOptionSkip(final String skip) throws UriSyntaxException { |
| try { |
| uriResult.setSkip(Integer.valueOf(skip)); |
| } catch (NumberFormatException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDVALUE.addContent(skip), e); |
| } |
| |
| if (skip.startsWith("-")) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDNEGATIVEVALUE.addContent(skip)); |
| } else if (skip.startsWith("+")) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDVALUE.addContent(skip)); |
| } |
| } |
| |
| private void handleSystemQueryOptionTop(final String top) throws UriSyntaxException { |
| try { |
| uriResult.setTop(Integer.valueOf(top)); |
| } catch (NumberFormatException e) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDVALUE.addContent(top), e); |
| } |
| |
| if (top.startsWith("-")) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDNEGATIVEVALUE.addContent(top)); |
| } else if (top.startsWith("+")) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDVALUE.addContent(top)); |
| } |
| } |
| |
| private void handleSystemQueryOptionExpand(final String expandStatement) throws UriSyntaxException, |
| UriNotMatchingException, EdmException { |
| ArrayList<ArrayList<NavigationPropertySegment>> expand = new ArrayList<ArrayList<NavigationPropertySegment>>(); |
| |
| if (expandStatement.startsWith(",") || expandStatement.endsWith(",")) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| for (String expandItemString : expandStatement.split(",")) { |
| expandItemString = expandItemString.trim(); |
| if ("".equals(expandItemString)) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| if (expandItemString.startsWith("/") || expandItemString.endsWith("/")) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| ArrayList<NavigationPropertySegment> expandNavigationProperties = new ArrayList<NavigationPropertySegment>(); |
| EdmEntitySet fromEntitySet = uriResult.getTargetEntitySet(); |
| |
| for (String expandPropertyName : expandItemString.split("/")) { |
| if ("".equals(expandPropertyName)) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| final EdmTyped property = fromEntitySet.getEntityType().getProperty(expandPropertyName); |
| if (property == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.PROPERTYNOTFOUND.addContent(expandPropertyName)); |
| } |
| if (property.getType().getKind() == EdmTypeKind.ENTITY) { |
| final EdmNavigationProperty navigationProperty = (EdmNavigationProperty) property; |
| fromEntitySet = fromEntitySet.getRelatedEntitySet(navigationProperty); |
| NavigationPropertySegmentImpl propertySegment = new NavigationPropertySegmentImpl(); |
| propertySegment.setNavigationProperty(navigationProperty); |
| propertySegment.setTargetEntitySet(fromEntitySet); |
| expandNavigationProperties.add(propertySegment); |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.NONAVIGATIONPROPERTY.addContent(expandPropertyName)); |
| } |
| } |
| expand.add(expandNavigationProperties); |
| } |
| uriResult.setExpand(expand); |
| } |
| |
| private void handleSystemQueryOptionSelect(final String selectStatement) throws UriSyntaxException, |
| UriNotMatchingException, EdmException { |
| ArrayList<SelectItem> select = new ArrayList<SelectItem>(); |
| |
| if (selectStatement.startsWith(",") || selectStatement.endsWith(",")) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| for (String selectItemString : selectStatement.split(",")) { |
| selectItemString = selectItemString.trim(); |
| if ("".equals(selectItemString)) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| if (selectItemString.startsWith("/") || selectItemString.endsWith("/")) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| SelectItemImpl selectItem = new SelectItemImpl(); |
| boolean exit = false; |
| EdmEntitySet fromEntitySet = uriResult.getTargetEntitySet(); |
| |
| for (String selectedPropertyName : selectItemString.split("/")) { |
| if ("".equals(selectedPropertyName)) { |
| throw new UriSyntaxException(UriSyntaxException.EMPTYSEGMENT); |
| } |
| |
| if (exit) { |
| throw new UriSyntaxException(UriSyntaxException.INVALIDSEGMENT.addContent(selectItemString)); |
| } |
| |
| if ("*".equals(selectedPropertyName)) { |
| selectItem.setStar(true); |
| exit = true; |
| continue; |
| } |
| |
| final EdmTyped property = fromEntitySet.getEntityType().getProperty(selectedPropertyName); |
| if (property == null) { |
| throw new UriNotMatchingException(UriNotMatchingException.PROPERTYNOTFOUND.addContent(selectedPropertyName)); |
| } |
| |
| switch (property.getType().getKind()) { |
| case SIMPLE: |
| case COMPLEX: |
| selectItem.setProperty((EdmProperty) property); |
| exit = true; |
| break; |
| |
| case ENTITY: // navigation properties point to entities |
| final EdmNavigationProperty navigationProperty = (EdmNavigationProperty) property; |
| final EdmEntitySet targetEntitySet = fromEntitySet.getRelatedEntitySet(navigationProperty); |
| |
| NavigationPropertySegmentImpl navigationPropertySegment = new NavigationPropertySegmentImpl(); |
| navigationPropertySegment.setNavigationProperty(navigationProperty); |
| navigationPropertySegment.setTargetEntitySet(targetEntitySet); |
| selectItem.addNavigationPropertySegment(navigationPropertySegment); |
| |
| fromEntitySet = targetEntitySet; |
| break; |
| |
| default: |
| throw new UriSyntaxException(UriSyntaxException.INVALIDPROPERTYTYPE); |
| } |
| } |
| select.add(selectItem); |
| } |
| uriResult.setSelect(select); |
| } |
| |
| private void handleOtherQueryParameters() throws UriSyntaxException, EdmException { |
| final EdmFunctionImport functionImport = uriResult.getFunctionImport(); |
| if (functionImport != null) { |
| for (final String parameterName : functionImport.getParameterNames()) { |
| final EdmParameter parameter = functionImport.getParameter(parameterName); |
| final String value = otherQueryParameters.remove(parameterName); |
| |
| if (value == null) { |
| if (parameter.getFacets() == null || parameter.getFacets().isNullable() == null |
| || parameter.getFacets().isNullable()) { |
| continue; |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.MISSINGPARAMETER); |
| } |
| } |
| |
| EdmLiteral uriLiteral = parseLiteral(value, (EdmSimpleType) parameter.getType(), parameter.getFacets()); |
| uriResult.addFunctionImportParameter(parameterName, uriLiteral); |
| } |
| } |
| |
| uriResult.setCustomQueryOptions(otherQueryParameters); |
| } |
| |
| private EdmLiteral parseLiteral(String value, EdmSimpleType expectedType, EdmFacets facets) |
| throws UriSyntaxException { |
| EdmLiteral literal; |
| try { |
| literal = simpleTypeFacade.parseUriLiteral(value, facets); |
| } catch (EdmLiteralException e) { |
| throw convertEdmLiteralException(e); |
| } |
| |
| if (expectedType.isCompatible(literal.getType())) { |
| return literal; |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INCOMPATIBLELITERAL.addContent(value, expectedType)); |
| } |
| } |
| |
| private EdmLiteral parseLiteral(final String value, final EdmSimpleType expectedType) throws UriSyntaxException { |
| EdmLiteral literal; |
| try { |
| literal = simpleTypeFacade.parseUriLiteral(value); |
| } catch (EdmLiteralException e) { |
| throw convertEdmLiteralException(e); |
| } |
| |
| if (expectedType.isCompatible(literal.getType())) { |
| return literal; |
| } else { |
| throw new UriSyntaxException(UriSyntaxException.INCOMPATIBLELITERAL.addContent(value, expectedType)); |
| } |
| } |
| |
| private static UriSyntaxException convertEdmLiteralException(final EdmLiteralException e) { |
| final MessageReference messageReference = e.getMessageReference(); |
| |
| if (EdmLiteralException.LITERALFORMAT.equals(messageReference)) { |
| return new UriSyntaxException(UriSyntaxException.LITERALFORMAT.addContent(messageReference.getContent()), e); |
| } else if (EdmLiteralException.NOTEXT.equals(messageReference)) { |
| return new UriSyntaxException(UriSyntaxException.NOTEXT.addContent(messageReference.getContent()), e); |
| } else if (EdmLiteralException.UNKNOWNLITERAL.equals(messageReference)) { |
| return new UriSyntaxException(UriSyntaxException.UNKNOWNLITERAL.addContent(messageReference.getContent()), e); |
| } else { |
| return new UriSyntaxException(ODataBadRequestException.COMMON, e); |
| } |
| } |
| |
| private static List<String> copyPathSegmentList(final List<PathSegment> source) { |
| List<String> copy = new ArrayList<String>(); |
| |
| for (final PathSegment segment : source) { |
| copy.add(segment.getPath()); |
| } |
| |
| return copy; |
| } |
| |
| private static String percentDecode(final String value) throws UriSyntaxException { |
| try { |
| return Decoder.decode(value); |
| } catch (RuntimeException e) { |
| throw new UriSyntaxException(UriSyntaxException.URISYNTAX, e); |
| } |
| } |
| |
| @Override |
| public FilterExpression parseFilterString(final EdmEntityType entityType, final String expression) |
| throws ODataMessageException { |
| return new FilterParserImpl(entityType).parseFilterString(expression); |
| } |
| |
| @Override |
| public OrderByExpression parseOrderByString(final EdmEntityType entityType, final String expression) |
| throws ODataMessageException { |
| return new OrderByParserImpl(entityType).parseOrderByString(expression); |
| } |
| |
| @Override |
| public ExpandSelectTreeNode buildExpandSelectTree(final List<SelectItem> select, |
| final List<ArrayList<NavigationPropertySegment>> expand) throws EdmException { |
| return new ExpandSelectTreeCreator(select, expand).create(); |
| } |
| |
| @Override |
| protected PathSegment buildPathSegment(String path, Map<String, List<String>> matrixParameters) { |
| return new ODataPathSegmentImpl(path, matrixParameters); |
| } |
| |
| @Override |
| public List<KeyPredicate> getKeyFromEntityLink(final EdmEntitySet entitySet, final String entityLink, |
| final URI serviceRoot) throws ODataException { |
| final String relativeLink = serviceRoot == null ? entityLink : |
| entityLink.startsWith(serviceRoot.toString()) ? |
| entityLink.substring(serviceRoot.toString().length()) : entityLink; |
| final Matcher matcher = INITIAL_SEGMENT_PATTERN.matcher(relativeLink); |
| if (!matcher.matches()) { |
| throw new UriNotMatchingException(UriNotMatchingException.MATCHPROBLEM.addContent(relativeLink)); |
| } |
| |
| final String entityContainerName = percentDecode(matcher.group(1)); |
| if (entityContainerName == null && !entitySet.getEntityContainer().isDefaultEntityContainer() |
| || entityContainerName != null && !entityContainerName.equals(entitySet.getEntityContainer().getName())) { |
| throw new UriNotMatchingException(UriNotMatchingException.CONTAINERNOTFOUND.addContent(entityContainerName)); |
| } |
| |
| final String entitySetName = percentDecode(matcher.group(2)); |
| if (!entitySetName.equals(entitySet.getName())) { |
| throw new UriNotMatchingException(UriNotMatchingException.NOTFOUND.addContent(entitySetName)); |
| } |
| |
| final String keyPredicate = matcher.group(3); |
| if (keyPredicate == null) { |
| throw new UriSyntaxException(UriSyntaxException.ENTITYSETINSTEADOFENTITY.addContent(entitySetName)); |
| } |
| return parseKey(keyPredicate, entitySet.getEntityType()); |
| } |
| } |