| /* |
| * 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.serializer.utils; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.olingo.commons.api.edm.EdmAction; |
| import org.apache.olingo.commons.api.edm.EdmComplexType; |
| import org.apache.olingo.commons.api.edm.EdmEntityType; |
| 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.core.Encoder; |
| import org.apache.olingo.server.api.serializer.SerializerException; |
| import org.apache.olingo.server.api.uri.UriParameter; |
| import org.apache.olingo.server.api.uri.UriResource; |
| import org.apache.olingo.server.api.uri.UriResourceAction; |
| import org.apache.olingo.server.api.uri.UriResourceComplexProperty; |
| import org.apache.olingo.server.api.uri.UriResourceFunction; |
| import org.apache.olingo.server.api.uri.UriResourceNavigation; |
| import org.apache.olingo.server.api.uri.UriResourceProperty; |
| 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.SelectItem; |
| import org.apache.olingo.server.api.uri.queryoption.SelectOption; |
| |
| public final class ContextURLHelper { |
| |
| private ContextURLHelper() { /* private ctor for helper class */} |
| |
| /** |
| * Builds a list of selected Properties for the ContextURL, |
| * taking care to preserve the order as defined in the EDM; |
| * returns NULL if no selection has taken place. |
| * @param type the structured type |
| * @param expand the Expand option (from the URL's $expand query option) |
| * @param select the Select option (from the URL's $select query option) |
| * @return a select-list String |
| * @throws SerializerException if an unsupported feature is used |
| */ |
| public static String buildSelectList(final EdmStructuredType type, |
| final ExpandOption expand, final SelectOption select) throws SerializerException { |
| StringBuilder result = new StringBuilder(); |
| if (ExpandSelectHelper.hasSelect(select)) { |
| handleSelect(type, select, result); |
| } |
| |
| if (ExpandSelectHelper.hasExpand(expand) && !(null != ExpandSelectHelper.getExpandAll(expand))) { |
| handleExpand(type, expand, result); |
| }else if(expand != null && null != ExpandSelectHelper.getExpandAll(expand)){ |
| handleExpandAll(type, expand, result); |
| } |
| return result.length() == 0 ? null : result.toString(); |
| } |
| |
| private static void handleSelect(EdmStructuredType type, final SelectOption select, |
| final StringBuilder result) { |
| if (ExpandSelectHelper.isAll(select)) { |
| result.append('*'); |
| } else { |
| final List<SelectItem> selectItems = select.getSelectItems(); |
| type = getTypeFromSelectItems(selectItems, type); |
| final Set<String> selectedPropertyNames = ExpandSelectHelper.getSelectedPropertyNames(selectItems); |
| for (final String propertyName : type.getPropertyNames()) { |
| constructSelectItemList(type, result, selectItems, selectedPropertyNames, propertyName); |
| } |
| for (final String propertyName : type.getNavigationPropertyNames()) { |
| constructSelectItemList(type, result, selectItems, selectedPropertyNames, propertyName); |
| } |
| constructSelectItemListForActionsAndFunctions(type, result, selectItems); |
| } |
| } |
| |
| /** |
| * This constructs the result based on the qualified action name |
| * and qualified function name specified in the select option of the url |
| * @param type |
| * @param result |
| * @param selectItems |
| */ |
| private static void constructSelectItemListForActionsAndFunctions(EdmStructuredType type, StringBuilder result, |
| List<SelectItem> selectItems) { |
| for (SelectItem item : selectItems) { |
| final UriResource resource = item.getResourcePath().getUriResourceParts().get(0); |
| if (resource instanceof UriResourceAction) { |
| EdmAction action = ((UriResourceAction)resource).getAction(); |
| if (action != null && action.isBound()) { |
| String actionBindingParamType = action.getBindingParameterTypeFqn(). |
| getFullQualifiedNameAsString(); |
| if (type.getFullQualifiedName().getFullQualifiedNameAsString(). |
| equalsIgnoreCase(actionBindingParamType)) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(action.getFullQualifiedName().getFullQualifiedNameAsString())); |
| } |
| } |
| } else if (resource instanceof UriResourceFunction) { |
| EdmFunction function = ((UriResourceFunction)resource).getFunction(); |
| if (function != null && function.isBound()) { |
| String functionBindingParamType = function.getBindingParameterTypeFqn(). |
| getFullQualifiedNameAsString(); |
| if (type.getFullQualifiedName().getFullQualifiedNameAsString(). |
| equalsIgnoreCase(functionBindingParamType)) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(function.getFullQualifiedName().getFullQualifiedNameAsString())); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This fetches the correct EntityType if there is an entity type cast |
| * specified in the select option of the url |
| * @param selectItems |
| * @param type |
| * @return |
| */ |
| private static EdmStructuredType getTypeFromSelectItems(List<SelectItem> selectItems, EdmStructuredType type) { |
| EdmStructuredType edmType = type; |
| for (final SelectItem item : selectItems) { |
| if (item.getStartTypeFilter() != null && item.getStartTypeFilter() instanceof EdmEntityType) { |
| edmType = (EdmEntityType) item.getStartTypeFilter(); |
| } |
| } |
| return edmType; |
| } |
| |
| /** |
| * @param type |
| * @param result |
| * @param selectItems |
| * @param selectedPropertyNames |
| * @param propertyName |
| */ |
| private static void constructSelectItemList(final EdmStructuredType type, final StringBuilder result, |
| final List<SelectItem> selectItems, final Set<String> selectedPropertyNames, final String propertyName) { |
| if (selectedPropertyNames.contains(propertyName)) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| final EdmProperty edmProperty = type.getStructuralProperty(propertyName); |
| final Set<List<String>> selectedPaths = ExpandSelectHelper. |
| getSelectedPathsWithTypeCasts(selectItems, propertyName); |
| if (selectedPaths == null) { |
| result.append(Encoder.encode(propertyName)); |
| } else { |
| List<List<String>> complexSelectedPaths = edmProperty != null && |
| edmProperty.getType() instanceof EdmComplexType ? |
| getComplexSelectedPaths(edmProperty, selectedPaths) : new ArrayList<>(); |
| if (complexSelectedPaths.isEmpty()) { |
| for (List<String> path : selectedPaths) { |
| complexSelectedPaths.add(path); |
| } |
| int position = getPositionToAddProperty(selectItems, propertyName, selectedPaths); |
| if (position == -1) { |
| complexSelectedPaths.get(0).add(propertyName); |
| } else { |
| complexSelectedPaths.get(0).add(position, propertyName); |
| } |
| } |
| |
| boolean first = true; |
| for (final List<String> path : complexSelectedPaths) { |
| if (first) { |
| first = false; |
| } else { |
| result.append(','); |
| } |
| boolean innerFirst = true; |
| for (final String name : path) { |
| if (innerFirst) { |
| innerFirst = false; |
| } else { |
| result.append('/'); |
| } |
| result.append(Encoder.encode(name)); |
| } |
| } |
| } |
| } else { |
| if (type instanceof EdmEntityType) { |
| final List<String> keyNames = ((EdmEntityType) type).getKeyPredicateNames(); |
| if (keyNames.contains(propertyName)) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(propertyName)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * If there is a type filter on a complex property (complex type cast) or |
| * a type filter on a navigation property, this method finds the appropriate position |
| * to add the complex property or the navigation property in the result object |
| * @param selectItems |
| * @param propertyName |
| * @param selectedPaths |
| * @return |
| */ |
| private static int getPositionToAddProperty(List<SelectItem> selectItems, String propertyName, |
| Set<List<String>> selectedPaths) { |
| for (SelectItem item : selectItems) { |
| final List<UriResource> parts = item.getResourcePath().getUriResourceParts(); |
| int i = 0; |
| for (UriResource part : parts) { |
| if (part instanceof UriResourceComplexProperty && |
| ((UriResourceComplexProperty) part).getProperty().getName().equalsIgnoreCase(propertyName)) { |
| if (((UriResourceComplexProperty)part).getComplexTypeFilter() != null) { |
| return getComplexPropertyPosition(selectedPaths, (UriResourceComplexProperty)part); |
| } else { |
| return i; |
| } |
| } else if (part instanceof UriResourceNavigation && |
| ((UriResourceNavigation) part).getProperty().getName().equalsIgnoreCase(propertyName)) { |
| return -1; |
| } |
| i++; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * @param selectedPaths |
| * @param part |
| */ |
| private static int getComplexPropertyPosition(Set<List<String>> selectedPaths, UriResourceComplexProperty part) { |
| for (List<String> pathSel : selectedPaths) { |
| int i = 0; |
| for (String sel : pathSel) { |
| if (sel.equalsIgnoreCase(part.getComplexTypeFilter(). |
| getFullQualifiedName().getFullQualifiedNameAsString())) { |
| return i; |
| } |
| i++; |
| } |
| } |
| return 0; |
| } |
| |
| private static void handleExpand(final EdmStructuredType type, final ExpandOption expand, final StringBuilder result) |
| throws SerializerException { |
| final Set<String> expandedPropertyNames = ExpandSelectHelper.getExpandedPropertyNames(expand.getExpandItems()); |
| for (final String propertyName : type.getNavigationPropertyNames()) { |
| if (expandedPropertyNames.contains(propertyName)) { |
| |
| |
| final ExpandItem expandItem = ExpandSelectHelper.getExpandItem(expand.getExpandItems(), propertyName); |
| if (ExpandSelectHelper.hasExpand(expandItem.getExpandOption()) |
| && !(null != ExpandSelectHelper.getExpandAll(expandItem.getExpandOption())) |
| || ExpandSelectHelper.hasSelect(expandItem.getSelectOption())) { |
| final String innerSelectList = buildSelectList(type.getNavigationProperty(propertyName).getType(), |
| expandItem.getExpandOption(), expandItem.getSelectOption()); |
| if (innerSelectList != null) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(propertyName)).append('(').append(innerSelectList).append(')'); |
| } |
| } else { |
| final List<UriResource> resourceParts = expandItem.getResourcePath().getUriResourceParts(); |
| if (resourceParts.size() > 1) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| final List<String> path = getPropertyPath(resourceParts); |
| String propertyPath = buildPropertyPath(path); |
| result.append(Encoder.encode(propertyName)); |
| result.append("/").append(propertyPath); |
| } else { |
| appendExpandedProperty(result, propertyName); |
| } |
| } |
| |
| |
| } |
| } |
| } |
| |
| private static void handleExpandAll(final EdmStructuredType type, |
| final ExpandOption expand, final StringBuilder result) throws SerializerException { |
| for (final String propertyName : type.getNavigationPropertyNames()) { |
| appendExpandedProperty(result, propertyName); |
| } |
| } |
| |
| private static void appendExpandedProperty(StringBuilder result, String propertyName) |
| throws SerializerException { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(propertyName) + "()"); |
| } |
| |
| private static List<String> getPropertyPath(final List<UriResource> path) { |
| List<String> result = new LinkedList<>(); |
| int index = 1; |
| while (index < path.size() && path.get(index) instanceof UriResourceProperty) { |
| result.add(((UriResourceProperty) path.get(index)).getProperty().getName()); |
| index++; |
| } |
| return result; |
| } |
| |
| private static String buildPropertyPath(final List<String> path) { |
| StringBuilder result = new StringBuilder(); |
| for (final String segment : path) { |
| result.append(result.length() == 0 ? "" : '/').append(Encoder.encode(segment)); //$NON-NLS-1$ |
| } |
| return result.length() == 0 ? null : result.toString(); |
| } |
| |
| private static List<List<String>> getComplexSelectedPaths(final EdmProperty edmProperty, |
| final Set<List<String>> selectedPaths) { |
| List<List<String>> result = new ArrayList<>(); |
| if (selectedPaths == null) { |
| List<String> path = new LinkedList<>(); |
| path.add(edmProperty.getName()); |
| result.add(path); |
| } else { |
| final EdmComplexType type = (EdmComplexType) edmProperty.getType(); |
| for (final String complexPropertyName : type.getPropertyNames()) { |
| if (ExpandSelectHelper.isSelected(selectedPaths, complexPropertyName)) { |
| List<List<String>> complexSelectedPaths = getComplexSelectedPaths( |
| (EdmProperty) type.getProperty(complexPropertyName), |
| ExpandSelectHelper.getReducedSelectedPaths(selectedPaths, complexPropertyName)); |
| for (final List<String> path : complexSelectedPaths) { |
| path.add(0, edmProperty.getName()); |
| result.add(path); |
| } |
| } |
| } |
| for (final String complexPropertyName : type.getNavigationPropertyNames()) { |
| if (ExpandSelectHelper.isSelected(selectedPaths, complexPropertyName)) { |
| List<List<String>> complexSelectedPaths = getComplexSelectedPaths( |
| (EdmNavigationProperty) type.getProperty(complexPropertyName)); |
| for (final List<String> path : complexSelectedPaths) { |
| path.add(0, edmProperty.getName()); |
| result.add(path); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static List<List<String>> getComplexSelectedPaths(EdmNavigationProperty edmProperty) { |
| List<List<String>> result = new ArrayList<>(); |
| List<String> path = new LinkedList<>(); |
| path.add(edmProperty.getName()); |
| result.add(path); |
| return result; |
| } |
| |
| /** |
| * Builds a key predicate for the ContextURL. |
| * @param keys the keys as a list of {@link UriParameter} instances |
| * @return a String with the key predicate |
| */ |
| public static String buildKeyPredicate(final List<UriParameter> keys) throws SerializerException { |
| if (keys == null || keys.isEmpty()) { |
| return null; |
| } else if (keys.size() == 1) { |
| return Encoder.encode(keys.get(0).getText()); |
| } else { |
| StringBuilder result = new StringBuilder(); |
| for (final UriParameter key : keys) { |
| if (result.length() > 0) { |
| result.append(','); |
| } |
| result.append(Encoder.encode(key.getName())).append('=').append(Encoder.encode(key.getText())); |
| } |
| return result.toString(); |
| } |
| } |
| } |