[OLINGO-801] UriParser supports Complex / Entity Parameters.

This commit is contained in:
Christian Holzer 2015-10-15 10:01:55 +02:00
parent f221962f72
commit 3584e1d71d
15 changed files with 353 additions and 90 deletions

View File

@ -29,6 +29,7 @@ STRING : '\'' -> more, pushMode(MODE_STRING) ;
QUOTATION_MARK : '\u0022' -> more, pushMode(MODE_JSON_STRING); //reads up to next unescaped "
SEARCH_INLINE : '$search' -> pushMode(MODE_SYSTEM_QUERY_SEARCH); //
FRAGMENT : '#' -> pushMode(MODE_FRAGMENT); //
STRING_JSON : '"' -> more, pushMode(MODE_JSON_STRING); //reads up to next unescaped "
GEOGRAPHY : G E O G R A P H Y SQUOTE -> pushMode(MODE_ODATA_GEO); //TODO make case insensitive
GEOMETRY : G E O M E T R Y SQUOTE -> pushMode(MODE_ODATA_GEO);
@ -364,8 +365,11 @@ mode MODE_JSON_STRING;
// Any """ characters inside a string are escaped with "\".
//;==============================================================================
STRING_IN_JSON : ('\\"' | ~[\u0022] )* '"' -> popMode;
ERROR_CHARACTER_jsm : EOF | .;
STRING_IN_JSON : (ESCAPED_JSON_CHAR | ~["\\])* '"' -> popMode;
fragment ESCAPED_JSON_CHAR : '\\' (["\\/bfnrt] | UNICODE_CHAR);
fragment UNICODE_CHAR : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT;
fragment HEX_DIGIT : [0-9a-fA-F];
ERROR_CHARACTER_jsm : EOF | .;
//;==============================================================================
mode MODE_ODATA_GEO;

View File

@ -205,6 +205,7 @@ commonExpr : OPEN commonExpr CLOSE
| rootExpr #altRoot // $...
| AT odataIdentifier #altAlias // @...
| primitiveLiteral #altLiteral // ...
| arrayOrObject #altJson
;
unary : (MINUS| NOT) ;
@ -309,11 +310,15 @@ json_value : jsonPrimitiv
| json_array;
json_object : BEGIN_OBJECT
STRING_IN_JSON
WSP? COLON WSP?
json_value
(
json_key_value_pair
(COMMA json_key_value_pair)*
)?
END_OBJECT;
json_key_value_pair : STRING_IN_JSON
WSP? COLON WSP?
json_value;
//; JSON syntax: adapted to URI restrictions from [RFC4627]
jsonPrimitiv : STRING_IN_JSON

View File

@ -25,8 +25,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoAll;
import org.apache.olingo.server.api.uri.UriInfoBatch;
@ -36,6 +36,7 @@ import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriInfoMetadata;
import org.apache.olingo.server.api.uri.UriInfoResource;
import org.apache.olingo.server.api.uri.UriInfoService;
import org.apache.olingo.server.api.uri.UriParameter;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.CountOption;
import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption;
@ -63,7 +64,7 @@ public class UriInfoImpl implements UriInfo {
private EdmEntityType entityTypeCast; // for $entity
private List<CustomQueryOptionImpl> customQueryOptions = new ArrayList<CustomQueryOptionImpl>();
private Map<String, String> aliasToValue = new HashMap<String, String>();
private Map<String, UriParameter> aliasValues = new HashMap<String, UriParameter>();
private Map<SystemQueryOptionKind, SystemQueryOption> systemQueryOptions =
new HashMap<SystemQueryOptionKind, SystemQueryOption>();
@ -138,7 +139,16 @@ public class UriInfoImpl implements UriInfo {
@Override
public String getValueForAlias(final String alias) {
return aliasToValue.get(alias);
final UriParameter parameter = aliasValues.get(alias);
return parameter == null ? null : parameter.getText();
}
public UriParameter getAlias(final String key) {
return aliasValues.get(key);
}
public void addAlias(final String key, UriParameter parameter) {
aliasValues.put(key, parameter);
}
@Override
@ -235,9 +245,6 @@ public class UriInfoImpl implements UriInfo {
public void addCustomQueryOption(final CustomQueryOptionImpl item) {
customQueryOptions.add(item);
if (item.getName().startsWith("@")) {
aliasToValue.put(item.getName(), item.getText());
}
}
/**
@ -297,5 +304,4 @@ public class UriInfoImpl implements UriInfo {
public Collection<SystemQueryOption> getSystemQueryOptions() {
return Collections.unmodifiableCollection(systemQueryOptions.values());
}
}

View File

@ -30,8 +30,8 @@ import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriResource;
@ -42,6 +42,7 @@ import org.apache.olingo.server.api.uri.UriResourceValue;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOption;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.UriParameterImpl;
import org.apache.olingo.server.core.uri.antlr.UriLexer;
import org.apache.olingo.server.core.uri.antlr.UriParserParser;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.AllEOFContext;
@ -65,11 +66,14 @@ import org.apache.olingo.server.core.uri.queryoption.SelectOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.SkipOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.SkipTokenOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.TopOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.ExpressionImpl;
public class Parser {
private static final String ATOM = "atom";
private static final String JSON = "json";
private static final String XML = "xml";
private static final String AT = "@";
private static final String NULL = "null";
int logLevel = 0;
private enum ParserEntryRules {
@ -271,7 +275,26 @@ public class Parser {
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, option.name);
}
} else {
CustomQueryOptionImpl customOption = new CustomQueryOptionImpl();
if (option.name.startsWith(AT)) {
final FilterExpressionEOFContext filterExpCtx =
(FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression);
final ExpressionImpl expression = ((FilterOptionImpl) uriParseTreeVisitor
.visitFilterExpressionEOF(filterExpCtx)).getExpression();
final UriParameterImpl parameter = new UriParameterImpl();
parameter.setAlias(option.name);
parameter.setExpression(expression);
parameter.setText(NULL.equals(option.value) ? null : option.value);
if(context.contextUriInfo.getAlias(option.name) == null) {
context.contextUriInfo.addAlias(option.name, parameter);
} else {
throw new UriParserSyntaxException("Alias already specified! Name: " + option.name,
UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS, option.name);
}
}
final CustomQueryOptionImpl customOption = new CustomQueryOptionImpl();
customOption.setName(option.name);
customOption.setText(option.value);
context.contextUriInfo.addCustomQueryOption(customOption);

View File

@ -96,6 +96,11 @@ public class UriContext {
public UriInfoImpl contextUriInfo;
public boolean contextReadingFunctionParameters;
/**
* Set to true if the parser operates on query part.
*/
public boolean contextReadingQueryPart;
public UriContext() {
contextExpandItemPath = null;

View File

@ -95,6 +95,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltHasContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltMultContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltOrContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.AnyExprContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ArrayOrObjectContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.BatchEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.BinaryLiteralContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.BooleanNonCaseLiteralContext;
@ -116,6 +117,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.EnumLiteralContex
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandCountOptionContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandOptionContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathExtensionContext;
@ -179,6 +181,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.TotalOffsetMinute
import org.apache.olingo.server.core.uri.antlr.UriParserParser.TotalsecondsMethodCallExprContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.TrimMethodCallExprContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.YearMethodCallExprContext;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException.MessageKeys;
import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
@ -306,7 +309,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
final boolean checkFirst =
context.contextUriInfo.getLastResourcePart() == null
|| context.contextUriInfo.getLastResourcePart() instanceof UriResourceRootImpl;
|| context.contextUriInfo.getLastResourcePart() instanceof UriResourceRootImpl;
String odi = ctx.vODI.getText();
@ -361,6 +364,12 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
// check FunctionImport
EdmFunctionImport edmFunctionImport = edmEntityContainer.getFunctionImport(odi);
if(edmFunctionImport != null && context.contextReadingQueryPart) {
throw wrap(new UriParserSemanticException("Function Imports are not allowed in $filter or $orderby",
UriParserSemanticException.MessageKeys.FUNCTION_IMPORT_NOT_ALLOWED, odi));
}
if (edmFunctionImport != null
&& (parts.isEmpty() || !(parts.get(0) instanceof UriResourcePartTyped)
|| parts.get(0) instanceof UriResourceRoot)) {
@ -466,8 +475,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
throw wrap(new UriParserSemanticException("Property '" + odi + "' not found in type '"
+ structType.getFullQualifiedName().getFullQualifiedNameAsString() + "'",
ctx.depth() > 2 ? // path evaluation inside an expression or for the resource path?
UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE :
UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE,
UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE
: UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE,
structType.getFullQualifiedName().getFullQualifiedNameAsString(), odi));
}
@ -667,7 +676,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
// do a check for bound functions (which requires a parameter list)
if (ctx.vlNVO.size() == 0) {
throw wrap(new UriParserSemanticException("Unknown type for type cast " + fullFilterName.toString()
+ " not found", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE , fullFilterName.toString()));
+ " not found", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, fullFilterName.toString()));
}
context.contextReadingFunctionParameters = true;
@ -718,11 +727,11 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
* @param vNS namespace or null
*/
private void ensureNamespaceIsNull(final NamespaceContext vNS) {
if(vNS != null && context.contextUriInfo.getLastResourcePart() == null) {
if (vNS != null && context.contextUriInfo.getLastResourcePart() == null) {
// First resource part and namespace is not null!
throw wrap(new UriParserSemanticException("Namespace is not allowed for EntitySets, Singeltons, "
+ " Action Imports and Function Imports. Found " + vNS.getText(),
UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, vNS.getText()));
UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, vNS.getText()));
}
}
@ -1237,10 +1246,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
levels.setText(ctx.vM.getText());
try {
expandItem.setSystemQueryOption(levels);
} catch(ODataRuntimeException e) {
} catch (ODataRuntimeException e) {
// Thrown if duplicated system query options are detected
throw wrap(new UriParserSyntaxException("Double system query option!", e,
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
}
} else if (ctx.vL != null) {
LevelsOptionImpl levels = new LevelsOptionImpl();
@ -1249,10 +1258,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
levels.setValue(Integer.parseInt(text));
try {
expandItem.setSystemQueryOption(levels);
} catch(ODataRuntimeException e) {
} catch (ODataRuntimeException e) {
// Thrown if duplicated system query options are detected
throw wrap(new UriParserSyntaxException("Double system query option!", e,
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
}
}
@ -1269,10 +1278,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
for (SystemQueryOptionImpl option : list) {
expandItem.setSystemQueryOption(option);
}
} catch(ODataRuntimeException e) {
} catch (ODataRuntimeException e) {
// Thrown if duplicated system query options are detected
throw wrap(new UriParserSyntaxException("Double system query option!", e,
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage()));
}
context.contextExpandItemPath = contextExpandItemPathBU;
}
@ -1400,14 +1409,22 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitFilter(final FilterContext ctx) {
context.contextReadingQueryPart = true;
final FilterOptionImpl result = new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(2)
.accept(this));
context.contextReadingQueryPart = false;
return new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(2).accept(this));
return result;
}
@Override
public Object visitFilterExpressionEOF(final FilterExpressionEOFContext ctx) {
context.contextReadingQueryPart = true;
final FilterOptionImpl result = new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(0)
.accept(this));
context.contextReadingQueryPart = false;
return new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(0).accept(this));
return result;
}
@Override
@ -1711,7 +1728,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
throw wrap(new UriParserSemanticException("Parameters list on untyped resource path segment not allowed",
UriParserSemanticException.MessageKeys.PARAMETERS_LIST_ONLY_FOR_TYPED_PARTS));
}
if(last instanceof UriResourceFunction) {
if (last instanceof UriResourceFunction) {
final UriResourceFunction uriResourceFunction = (UriResourceFunction) context.contextUriInfo
.getLastResourcePart();
final EdmReturnType returnType = uriResourceFunction.getFunction().getReturnType();
@ -1726,7 +1743,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
final EdmEntityType entityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType();
final List<String> lastKeyPredicates = entityType.getKeyPredicateNames();
if(lastKeyPredicates.size() == list.size()) {
if (lastKeyPredicates.size() == list.size()) {
return list;
} else {
throw wrap(new UriParserSemanticException("Wrong number of key properties.",
@ -1825,8 +1842,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitNaninfinityLiteral(final NaninfinityLiteralContext ctx) {
return new LiteralImpl().setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)).
setText(ctx.getText());
return new LiteralImpl().setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)).setText(ctx
.getText());
}
@Override
@ -1863,6 +1880,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitOrderByEOF(final OrderByEOFContext ctx) {
context.contextReadingQueryPart = true;
OrderByOptionImpl orderBy = new OrderByOptionImpl();
@ -1871,6 +1889,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
orderBy.addOrder(oItem);
}
context.contextReadingFunctionParameters = false;
return orderBy;
}
@ -1955,7 +1974,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
return child1.accept(this);
}
// TODO Implement geography types and set the proper type
// TODO Implement geography types and set a proper type
return new LiteralImpl().setText(ctx.getText());
}
@ -1968,14 +1987,14 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitStringLiteral(final StringLiteralContext ctx) {
return new LiteralImpl().setText(ctx.getText())
.setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String));
.setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String));
}
@Override
public Object visitDecimalLiteral(final DecimalLiteralContext ctx) {
final EdmType type = EdmPrimitiveTypeFactory.getInstance(
ctx.getText().contains("e") || ctx.getText().contains("E") ?
EdmPrimitiveTypeKind.Double : EdmPrimitiveTypeKind.Decimal);
ctx.getText().contains("e") || ctx.getText().contains("E") ? EdmPrimitiveTypeKind.Double
: EdmPrimitiveTypeKind.Decimal);
return new LiteralImpl().setText(ctx.getText()).setType(type);
}
@ -1999,7 +2018,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
}
return new LiteralImpl().setText(ctx.getText()).setType(type);
} catch( NumberFormatException e) {
} catch (NumberFormatException e) {
return new LiteralImpl().setText(ctx.getText())
.setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal));
}
@ -2012,7 +2031,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
}
@Override
public Object visitDatetimeoffsetLiteral(final DatetimeoffsetLiteralContext ctx) {
public Object visitDatetimeoffsetLiteral(final DatetimeoffsetLiteralContext ctx) {
return new LiteralImpl().setText(ctx.getText())
.setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.DateTimeOffset));
}
@ -2124,13 +2143,17 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitSelectEOF(final SelectEOFContext ctx) {
context.contextReadingQueryPart = true;
List<SelectItemImpl> selectItems = new ArrayList<SelectItemImpl>();
for (SelectItemContext si : ctx.vlSI) {
selectItems.add((SelectItemImpl) si.accept(this));
}
return new SelectOptionImpl().setSelectItems(selectItems).setText(ctx.getText());
final QueryOptionImpl result = new SelectOptionImpl().setSelectItems(selectItems).setText(ctx.getText());
context.contextReadingQueryPart = false;
return result;
}
@Override
@ -2496,6 +2519,25 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor<Object> {
@Override
public Object visitSearchSpecialToken(final SearchSpecialTokenContext ctx) {
throw wrap(new UriParserSemanticException("System query option '$search' not implemented!",
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, "System query option '$search"));
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, "System query option '$search"));
}
@Override
public Object visitArrayOrObject(final ArrayOrObjectContext ctx) {
if (!context.contextReadingQueryPart) {
throw wrap(new UriParserSemanticException("Complex parameter are not allowed in resource path",
MessageKeys.COMPLEX_PARAMETER_IN_RESOURCE_PATH, ctx.getText()));
}
return new LiteralImpl().setText(ctx.getText()).setType(null);
}
@Override
public Object visitExpandItemsEOF(ExpandItemsEOFContext ctx) {
context.contextReadingQueryPart = true;
final Object result = super.visitExpandItemsEOF(ctx);
context.contextReadingQueryPart = false;
return result;
}
}

View File

@ -79,7 +79,11 @@ public class UriParserSemanticException extends UriParserException {
/** parameter: not implemented part */
NOT_IMPLEMENTED,
/** parameter: namespace **/
NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT;
NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT,
/** parameter: complex parameter value */
COMPLEX_PARAMETER_IN_RESOURCE_PATH,
/** parameter: function import name */
FUNCTION_IMPORT_NOT_ALLOWED;
@Override
public String getKey() {

View File

@ -35,7 +35,9 @@ public class UriParserSyntaxException extends UriParserException {
/** parameter: $format option value */
WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT,
SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE,
SYNTAX;
SYNTAX,
/** parameter: alias name */
DUPLICATED_ALIAS;
@Override
public String getKey() {

View File

@ -37,6 +37,8 @@ public class UriValidationException extends ODataLibraryException {
UNSUPPORTED_ACTION_RETURN_TYPE,
/** parameter: unsupported http method */
UNSUPPORTED_HTTP_METHOD,
/** parameter: unsupported parameter name */
UNSUPPORTED_PARAMETER,
/** parameter: system query option */
SYSTEM_QUERY_OPTION_NOT_ALLOWED,
/** parameters: system query option, http method */
@ -54,7 +56,10 @@ public class UriValidationException extends ODataLibraryException {
/** parameter: unallowed kind before $count */
UNALLOWED_KIND_BEFORE_COUNT,
/** parameter: unallowed resource path */
UNALLOWED_RESOURCE_PATH;
UNALLOWED_RESOURCE_PATH,
/** parameter: missing parameter name */
MISSING_PARAMETER;
@Override
public String getKey() {

View File

@ -139,6 +139,7 @@ public class UriValidator {
validateForHttpMethod(uriInfo, httpMethod);
}
validateQueryOptions(uriInfo);
validateParameters(uriInfo);
validateKeyPredicates(uriInfo);
validatePropertyOperations(uriInfo, httpMethod);
}
@ -556,6 +557,58 @@ public class UriValidator {
return UriResourceKind.action == uriResourceParts.get(uriResourceParts.size() - 1).getKind();
}
private void validateParameters(final UriInfo uriInfo) throws UriValidationException {
for (UriResource pathSegment : uriInfo.getUriResourceParts()) {
final boolean isFunction = pathSegment.getKind() == UriResourceKind.function;
if(isFunction) {
final UriResourceFunction functionPathSegement = (UriResourceFunction) pathSegment;
final EdmFunction edmFuntion = functionPathSegement.getFunction();
final Map<String, UriParameter> parameters = new HashMap<String, UriParameter>();
for(final UriParameter parameter : functionPathSegement.getParameters()) {
parameters.put(parameter.getName(), parameter);
}
boolean firstParameter = true;
for(final String parameterName : edmFuntion.getParameterNames()) {
final UriParameter parameter = parameters.get(parameterName);
final boolean isNullable = edmFuntion.getParameter(parameterName).isNullable();
if(parameter != null) {
/** No alias, value explicit null */
if(parameter.getText() == null
&& parameter.getAlias() == null && !isNullable) {
throw new UriValidationException("Missing non nullable parameter " + parameterName,
UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName);
} else if(parameter.getText() == null && parameter.getAlias() != null) {
final String valueForAlias = uriInfo.getValueForAlias(parameter.getAlias());
/** Alias value is missing or explicit null **/
if(valueForAlias == null && !isNullable) {
throw new UriValidationException("Missing non nullable parameter " + parameterName,
UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName);
}
}
parameters.remove(parameterName);
} else if(!isNullable && !(firstParameter && edmFuntion.isBound())) {
// The first parameter of bound functions is implicit provided by the preceding path segment
throw new UriValidationException("Missing non nullable parameter " + parameterName,
UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName);
}
firstParameter = false;
}
if(!parameters.isEmpty()) {
final String parameterName = parameters.keySet().iterator().next();
throw new UriValidationException("Unsupported parameter " + parameterName,
UriValidationException.MessageKeys.UNSUPPORTED_PARAMETER, parameterName);
}
}
}
}
private void validateKeyPredicates(final UriInfo uriInfo) throws UriValidationException {
for (UriResource pathSegment : uriInfo.getUriResourceParts()) {
final boolean isEntitySet = pathSegment.getKind() == UriResourceKind.entitySet;

View File

@ -35,6 +35,7 @@ UriParserSyntaxException.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION=The system query op
UriParserSyntaxException.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT=The system query option '$format' must be either 'json', 'xml', 'atom', or a valid content type; the value '%1$s' is neither.
UriParserSyntaxException.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE=The system query option '$levels' is not allowed here.
UriParserSyntaxException.SYNTAX=The URI is malformed.
UriParserSyntaxException.DUPLICATED_ALIAS=Duplicated alias. An alias '%1$s' was already specified!.
UriParserSemanticException.FUNCTION_NOT_FOUND=The function import '%1$s' has no function with parameters '%2$s'.
UriParserSemanticException.RESOURCE_PART_ONLY_FOR_TYPED_PARTS='%1$s' is only allowed for typed parts.
@ -68,6 +69,8 @@ UriParserSemanticException.PREVIOUS_PART_TYPED=The previous part is typed.
UriParserSemanticException.RESOURCE_NOT_FOUND=Cannot find EntitySet, Singleton, ActionImport or FunctionImport with name '%1$s'.
UriParserSemanticException.NOT_IMPLEMENTED=%1$s is not implemented!
UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singeltons, Action Imports and Function Imports. Found '%1$s'.
UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments. Found: '%1$s'.
UriParserSemanticException.FUNCTION_IMPORT_NOT_ALLOWED=Function Imports are not allowed in $filter or $orderby. Found: '%1$s'.
UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported.
UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported.
@ -75,6 +78,7 @@ UriValidationException.UNSUPPORTED_URI_RESOURCE_KIND=The URI resource kind '%1$s
UriValidationException.UNSUPPORTED_FUNCTION_RETURN_TYPE=The function return type '%1$s' is not supported.
UriValidationException.UNSUPPORTED_ACTION_RETURN_TYPE=The action return type '%1$s' is not supported.
UriValidationException.UNSUPPORTED_HTTP_METHOD=The HTTP method '%1$s' is not supported.
UriValidationException.UNSUPPORTED_PARAMETER=The parameter '%1$s' is not supported.
UriValidationException.SYSTEM_QUERY_OPTION_NOT_ALLOWED=The system query option '%1$s' is not allowed.
UriValidationException.SYSTEM_QUERY_OPTION_NOT_ALLOWED_FOR_HTTP_METHOD=The system query option '%1$s' is not allowed for HTTP method '%2$s'.
UriValidationException.INVALID_KEY_PROPERTY=The key property '%1$s' is invalid.
@ -84,6 +88,7 @@ UriValidationException.SECOND_LAST_SEGMENT_NOT_TYPED=The second last segment '%1
UriValidationException.UNALLOWED_KIND_BEFORE_VALUE=The kind '%1$s' is not allowed before '$value'.
UriValidationException.UNALLOWED_KIND_BEFORE_COUNT=The kind '%1$s' is not allowed before '$count'.
UriValidationException.UNALLOWED_RESOURCE_PATH=The resource part '%1$s' is not allowed.
UriValidationException.MISSING_PARAMETER=Missing mandatory parameter '%1$s'.
ContentNegotiatorException.UNSUPPORTED_ACCEPT_TYPES=The content-type range '%1$s' is not supported as value of the Accept header.
ContentNegotiatorException.UNSUPPORTED_CONTENT_TYPE=The content type '%1$s' is not supported.

View File

@ -2036,7 +2036,8 @@ public class TestFullResourcePath {
testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@parameterAlias)", "@parameterAlias=1");
testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@parameterAlias)/$count", "@parameterAlias=1");
testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@invalidAlias)", "@validAlias=1");
testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=@invalidAlias)", "@validAlias=1")
.isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER);
}
@Test
@ -5659,6 +5660,93 @@ public class TestFullResourcePath {
.right().isLiteral("" + Long.MAX_VALUE).isLiteralType(EdmInt64.getInstance());
}
@Test
public void parameterAliasLiteralValidation() throws Exception {
testUri.run("ESAllPrim(PropertyInt16=@p1)", "@p1=1");
testUri.run("ESAllPrim(PropertyInt16=@p1)", "@p1=-2");
testUri.runEx("ESAllPrim(PropertyInt16=@p1)", "@p1='ewe'")
.isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY);
testUri.runEx("ESAllPrim(PropertyInt16=@p1)", "@p1='ewe")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
}
@Test
public void functionsWithComplexParameters() throws Exception {
testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}")
.goPath()
.at(0).isEntitySet("ESTwoKeyNav")
.at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1")
.isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"1\"}");
// Test JSON String lexer rule =\"3,Int16=abc},\\\nabc&test%test\b\f\r\t\u0022}
final String stringValueEncoded = "=\\\"3,Int16=abc},\\\\\\nabc%26test%25test\\b\\f\\r\\t\\u0022}";
final String stringValueDecoded = "=\\\"3,Int16=abc},\\\\\\nabc&test%test\\b\\f\\r\\t\\u0022}";
testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueEncoded + "\"}")
.goPath()
.at(0).isEntitySet("ESTwoKeyNav")
.at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1")
.isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueDecoded + "\"}");
testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp={\"PropertyString\":\"Test\",\"PropertyInt16\":1}) eq 'Test'")
.goFilter().left().is("<<BFCESTwoKeyNavRTStringParam> eq <'Test'>>")
.isParameterText(0, "{\"PropertyString\":\"Test\",\"PropertyInt16\":1}");
testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp={\"PropertyString\":\"" + stringValueEncoded + "\",\"PropertyInt16\":1}) eq 'Test'")
.goFilter().left().is("<<BFCESTwoKeyNavRTStringParam> eq <'Test'>>")
.isParameterText(0, "{\"PropertyString\":\"=\\\"3,Int16=abc},\\\\\\nabc&test%test\\b\\f\\r\\t\\u0022}\","
+ "\"PropertyInt16\":1}");
testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}");
testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":'1'}")
.isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX);
testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam"
+ "(ParameterComp={\"PropertyInt16\":1,\"PropertyString\":\"Test\"})")
.isExSemantic(UriParserSemanticException.MessageKeys.COMPLEX_PARAMETER_IN_RESOURCE_PATH);
testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=null)")
.isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER);
testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)")
.isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER);
testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null")
.isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER);
testUri.run("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test='null'");
testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test, UnknownParam=1)", "@test='null'")
.isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND);
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test='null'");
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null");
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)");
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=null)");
testUri.runEx("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null&@test='1'")
.isExSyntax(UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS);
testUri.runEx("ESAllPrim", "$filter=FINRTInt16() eq 0")
.isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_IMPORT_NOT_ALLOWED);
}
@Test
@Ignore("Key predicates in filter/orderby expression are not validated currently")
public void testKeyPredicatesInExpressions() throws Exception {
testUri.run("ESTwoKeyNav", "$filter=NavPropertyETTwoKeyNavMany(PropertyString='1',PropertyInt16=1)"
+ "/PropertyInt16 eq 1");
testUri.runEx("ESTwoKeyNav", "$filter=NavPropertyETTwoKeyNavMany(Prop='22',P=2)/PropertyInt16 eq 0")
.isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY);
}
public static String encode(final String decoded) throws UnsupportedEncodingException {
return Encoder.encode(decoded);
}

View File

@ -1061,7 +1061,7 @@ public class TestUriParserImpl {
@Test
public void testAlias() throws Exception {
testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1)")
testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1")
.goFilter().is("<<PropertyInt16> eq <@p1>>");
}

View File

@ -27,6 +27,7 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.provider.CsdlComplexType;
import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet;
import org.apache.olingo.commons.api.edm.provider.CsdlEntityType;
import org.apache.olingo.commons.api.edm.provider.CsdlFunctionImport;
import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty;
import org.apache.olingo.commons.api.edm.provider.CsdlProperty;
import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef;
@ -116,4 +117,12 @@ public class EdmTechTestProvider extends EdmTechProvider {
return super.getEntityType(entityTypeName);
}
@Override
public CsdlFunctionImport getFunctionImport(FullQualifiedName entityContainer, String functionImportName)
throws ODataException {
return super.getFunctionImport(entityContainer, functionImportName);
}
}

View File

@ -31,12 +31,14 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.UriResourceFunctionImpl;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
@ -403,16 +405,26 @@ public class FilterValidator implements TestValidator {
public FilterValidator isParameterText(final int parameterIndex, final String parameterText)
throws ExpressionVisitException, ODataApplicationException {
if (!(curExpression instanceof MethodImpl)) {
fail("Current expression is not a method");
if (curExpression instanceof MethodImpl) {
MethodImpl methodCall = (MethodImpl) curExpression;
Expression parameter = methodCall.getParameters().get(parameterIndex);
String actualParameterText = FilterTreeToText.Serialize(parameter);
assertEquals(parameterText, actualParameterText);
} else if (curExpression instanceof MemberImpl) {
final MemberImpl member = (MemberImpl) curExpression;
final List<UriResource> uriResourceParts = member.getResourcePath().getUriResourceParts();
if (!uriResourceParts.isEmpty() && uriResourceParts.get(0) instanceof UriResourceFunctionImpl) {
assertEquals(parameterText, ((UriResourceFunctionImpl) uriResourceParts.get(0)).getParameters()
.get(parameterIndex).getText());
} else {
fail("Current expression is not a method or function");
}
} else {
fail("Current expression is not a method or function");
}
MethodImpl methodCall = (MethodImpl) curExpression;
Expression parameter = methodCall.getParameters().get(parameterIndex);
String actualParameterText = FilterTreeToText.Serialize(parameter);
assertEquals(parameterText, actualParameterText);
return this;
}