blob: a22933fb66792d05a458c2f6e2e0499358987a41 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.parser;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.ParserRuleContext;
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.ODataRuntimeException;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.format.ODataFormat;
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.UriResourcePartTyped;
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.antlr.UriLexer;
import org.apache.olingo.server.core.uri.antlr.UriParserParser;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.AllEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.BatchEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.CrossjoinEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.EntityEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.FilterExpressionEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.MetadataEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.OrderByEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.PathSegmentEOFContext;
import org.apache.olingo.server.core.uri.antlr.UriParserParser.SelectEOFContext;
import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.CustomQueryOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.FilterOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.FormatOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.IdOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.OrderByOptionImpl;
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 java.util.ArrayList;
import java.util.List;
public class Parser {
int logLevel = 0;
private enum ParserEntryRules {
All, Batch, CrossJoin, Entity, ExpandItems, FilterExpression, Metadata, PathSegment, Orderby, Select
};
public Parser setLogLevel(final int logLevel) {
this.logLevel = logLevel;
return this;
}
public UriInfo parseUri(final String path, final String query, final String fragment, final Edm edm)
throws UriParserException {
UriContext context = new UriContext();
UriParseTreeVisitor uriParseTreeVisitor = new UriParseTreeVisitor(edm, context);
try {
final RawUri uri = UriDecoder.decodeUri(path, query, fragment, 0); // -> 0 segments are before the service url
// first, read the decoded path segments
final String firstSegment = uri.pathSegmentListDecoded.isEmpty() ? "" : uri.pathSegmentListDecoded.get(0);
if (firstSegment.isEmpty()) {
ensureLastSegment(firstSegment, 0, uri.pathSegmentListDecoded.size());
context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.service);
} else if (firstSegment.startsWith("$batch")) {
ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size());
BatchEOFContext ctxBatchEOF =
(BatchEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.Batch);
uriParseTreeVisitor.visitBatchEOF(ctxBatchEOF);
} else if (firstSegment.startsWith("$metadata")) {
ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size());
MetadataEOFContext ctxMetadataEOF =
(MetadataEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.Metadata);
uriParseTreeVisitor.visitMetadataEOF(ctxMetadataEOF);
context.contextUriInfo.setFragment(uri.fragment);
} else if (firstSegment.startsWith("$entity")) {
context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId);
if (uri.pathSegmentListDecoded.size() > 1) {
final String typeCastSegment = uri.pathSegmentListDecoded.get(1);
ensureLastSegment(typeCastSegment, 2, uri.pathSegmentListDecoded.size());
EntityEOFContext ctxEntityEOF =
(EntityEOFContext) parseRule(typeCastSegment, ParserEntryRules.Entity);
uriParseTreeVisitor.visitEntityEOF(ctxEntityEOF);
}
} else if (firstSegment.startsWith("$all")) {
ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size());
AllEOFContext ctxResourcePathEOF =
(AllEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.All);
uriParseTreeVisitor.visitAllEOF(ctxResourcePathEOF);
} else if (firstSegment.startsWith("$crossjoin")) {
ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size());
CrossjoinEOFContext ctxResourcePathEOF =
(CrossjoinEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.CrossJoin);
uriParseTreeVisitor.visitCrossjoinEOF(ctxResourcePathEOF);
} else {
List<PathSegmentEOFContext> ctxPathSegments = new ArrayList<PathSegmentEOFContext>();
for (String pathSegment : uri.pathSegmentListDecoded) {
PathSegmentEOFContext ctxPathSegment =
(PathSegmentEOFContext) parseRule(pathSegment, ParserEntryRules.PathSegment);
ctxPathSegments.add(ctxPathSegment);
}
context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.resource);
for (PathSegmentEOFContext ctxPathSegment : ctxPathSegments) {
// add checks for batch, entity, metadata, all, crossjoin
uriParseTreeVisitor.visitPathSegmentEOF(ctxPathSegment);
}
UriResource lastSegment = context.contextUriInfo.getLastResourcePart();
if (lastSegment instanceof UriResourcePartTyped) {
UriResourcePartTyped typed = (UriResourcePartTyped) lastSegment;
UriParseTreeVisitor.TypeInformation myType = uriParseTreeVisitor.getTypeInformation(typed);
UriParseTreeVisitor.TypeInformation typeInfo =
uriParseTreeVisitor.new TypeInformation(myType.type, typed.isCollection());
context.contextTypes.push(typeInfo);
}
}
// second, read the system query options and the custom query options
for (RawUri.QueryOption option : uri.queryOptionListDecoded) {
if (option.name.startsWith("$")) {
SystemQueryOption systemOption = null;
if (option.name.equals(SystemQueryOptionKind.FILTER.toString())) {
FilterExpressionEOFContext ctxFilterExpression =
(FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression);
FilterOptionImpl filterOption =
(FilterOptionImpl) uriParseTreeVisitor.visitFilterExpressionEOF(ctxFilterExpression);
systemOption = filterOption;
} else if (option.name.equals(SystemQueryOptionKind.FORMAT.toString())) {
FormatOptionImpl formatOption = new FormatOptionImpl();
formatOption.setName(option.name);
formatOption.setText(option.value);
if (option.value.equalsIgnoreCase(ODataFormat.JSON.name())
|| option.value.equalsIgnoreCase(ODataFormat.XML.name())
|| option.value.equalsIgnoreCase(ODataFormat.ATOM.name())
|| isFormatSyntaxValid(option.value)) {
formatOption.setFormat(option.value);
} else {
throw new UriParserSyntaxException("Illegal value of $format option!",
UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT, option.value);
}
systemOption = formatOption;
} else if (option.name.equals(SystemQueryOptionKind.EXPAND.toString())) {
ExpandItemsEOFContext ctxExpandItems =
(ExpandItemsEOFContext) parseRule(option.value, ParserEntryRules.ExpandItems);
ExpandOptionImpl expandOption =
(ExpandOptionImpl) uriParseTreeVisitor.visitExpandItemsEOF(ctxExpandItems);
systemOption = expandOption;
} else if (option.name.equals(SystemQueryOptionKind.ID.toString())) {
IdOptionImpl idOption = new IdOptionImpl();
idOption.setName(option.name);
idOption.setText(option.value);
idOption.setValue(option.value);
systemOption = idOption;
} else if (option.name.equals(SystemQueryOptionKind.LEVELS.toString())) {
throw new UriParserSyntaxException("System query option '$levels' is allowed only inside '$expand'!",
UriParserSyntaxException.MessageKeys.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE);
} else if (option.name.equals(SystemQueryOptionKind.ORDERBY.toString())) {
OrderByEOFContext ctxOrderByExpression =
(OrderByEOFContext) parseRule(option.value, ParserEntryRules.Orderby);
OrderByOptionImpl orderByOption =
(OrderByOptionImpl) uriParseTreeVisitor.visitOrderByEOF(ctxOrderByExpression);
systemOption = orderByOption;
} else if (option.name.equals(SystemQueryOptionKind.SEARCH.toString())) {
throw new RuntimeException("System query option '$search' not implemented!");
} else if (option.name.equals(SystemQueryOptionKind.SELECT.toString())) {
SelectEOFContext ctxSelectEOF =
(SelectEOFContext) parseRule(option.value, ParserEntryRules.Select);
SelectOptionImpl selectOption =
(SelectOptionImpl) uriParseTreeVisitor.visitSelectEOF(ctxSelectEOF);
systemOption = selectOption;
} else if (option.name.equals(SystemQueryOptionKind.SKIP.toString())) {
SkipOptionImpl skipOption = new SkipOptionImpl();
skipOption.setName(option.name);
skipOption.setText(option.value);
try {
skipOption.setValue(Integer.parseInt(option.value));
} catch (final NumberFormatException e) {
throw new UriParserSyntaxException("Illegal value of $skip option!", e,
UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION,
option.name, option.value);
}
systemOption = skipOption;
} else if (option.name.equals(SystemQueryOptionKind.SKIPTOKEN.toString())) {
SkipTokenOptionImpl skipTokenOption = new SkipTokenOptionImpl();
skipTokenOption.setName(option.name);
skipTokenOption.setText(option.value);
skipTokenOption.setValue(option.value);
systemOption = skipTokenOption;
} else if (option.name.equals(SystemQueryOptionKind.TOP.toString())) {
TopOptionImpl topOption = new TopOptionImpl();
topOption.setName(option.name);
topOption.setText(option.value);
try {
topOption.setValue(Integer.parseInt(option.value));
} catch (final NumberFormatException e) {
throw new UriParserSyntaxException("Illegal value of $top option!", e,
UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION,
option.name, option.value);
}
systemOption = topOption;
} else if (option.name.equals(SystemQueryOptionKind.COUNT.toString())) {
CountOptionImpl inlineCountOption = new CountOptionImpl();
inlineCountOption.setName(option.name);
inlineCountOption.setText(option.value);
if (option.value.equals("true") || option.value.equals("false")) {
inlineCountOption.setValue(Boolean.parseBoolean(option.value));
} else {
throw new UriParserSyntaxException("Illegal value of $count option!",
UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION,
option.name, option.value);
}
systemOption = inlineCountOption;
} else {
throw new UriParserSyntaxException("Unknown system query option!",
UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, option.name);
}
try {
context.contextUriInfo.setSystemQueryOption(systemOption);
} catch (final ODataRuntimeException e) {
throw new UriParserSyntaxException("Double system query option!", e,
UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, option.name);
}
} else {
CustomQueryOptionImpl customOption = new CustomQueryOptionImpl();
customOption.setName(option.name);
customOption.setText(option.value);
context.contextUriInfo.addCustomQueryOption(customOption);
}
}
return context.contextUriInfo;
} catch (ParseCancellationException e) {
throw e.getCause() instanceof UriParserException ?
(UriParserException) e.getCause() :
new UriParserSyntaxException("Syntax error", e, UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
private void ensureLastSegment(final String segment, final int pos, final int size)
throws UriParserSyntaxException {
if (pos < size) {
throw new UriParserSyntaxException(segment + " must be the last segment.",
UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT, segment);
}
}
private boolean isFormatSyntaxValid(final String value) {
final int index = value.indexOf('/');
return index > 0 && index < value.length() - 1 && index == value.lastIndexOf('/');
}
private ParserRuleContext parseRule(final String input, final ParserEntryRules entryPoint)
throws UriParserSyntaxException {
UriParserParser parser = null;
UriLexer lexer = null;
ParserRuleContext ret = null;
// Use 2 stage approach to improve performance
// see https://github.com/antlr/antlr4/issues/192
// stage = 1
try {
// create parser
if (logLevel > 0) {
lexer = new UriLexer(new ANTLRInputStream(input));
showTokens(input, lexer.getAllTokens());
}
lexer = new UriLexer(new ANTLRInputStream(input));
parser = new UriParserParser(new CommonTokenStream(lexer));
// Set error strategy
addStage1ErrorStategy(parser);
// Set error collector
addStage1ErrorListener(parser);
// user the faster LL parsing
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
// parse
switch (entryPoint) {
case All:
ret = parser.allEOF();
break;
case Batch:
ret = parser.batchEOF();
break;
case CrossJoin:
ret = parser.crossjoinEOF();
break;
case Metadata:
ret = parser.metadataEOF();
break;
case PathSegment:
ret = parser.pathSegmentEOF();
break;
case FilterExpression:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.filterExpressionEOF();
break;
case Orderby:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.orderByEOF();
break;
case ExpandItems:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.expandItemsEOF();
break;
case Entity:
ret = parser.entityEOF();
break;
case Select:
ret = parser.selectEOF();
break;
default:
break;
}
} catch (ParseCancellationException hardException) {
// stage = 2
try {
// create parser
lexer = new UriLexer(new ANTLRInputStream(input));
parser = new UriParserParser(new CommonTokenStream(lexer));
// Set error strategy
addStage2ErrorStategy(parser);
// Set error collector
addStage2ErrorListener(parser);
// Use the slower SLL parsing
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
// parse
switch (entryPoint) {
case All:
ret = parser.allEOF();
break;
case Batch:
ret = parser.batchEOF();
break;
case CrossJoin:
ret = parser.crossjoinEOF();
break;
case Metadata:
ret = parser.metadataEOF();
break;
case PathSegment:
ret = parser.pathSegmentEOF();
break;
case FilterExpression:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.filterExpressionEOF();
break;
case Orderby:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.orderByEOF();
break;
case ExpandItems:
lexer.mode(Lexer.DEFAULT_MODE);
ret = parser.expandItemsEOF();
break;
case Entity:
ret = parser.entityEOF();
break;
case Select:
ret = parser.selectEOF();
break;
default:
break;
}
} catch (final RecognitionException weakException) {
throw new UriParserSyntaxException("Error in syntax", weakException,
UriParserSyntaxException.MessageKeys.SYNTAX);
// exceptionOnStage = 2;
}
} catch (final RecognitionException hardException) {
throw new UriParserSyntaxException("Error in syntax", hardException,
UriParserSyntaxException.MessageKeys.SYNTAX);
}
return ret;
}
protected void addStage1ErrorStategy(final UriParserParser parser) {
// Throw exception at first syntax error
parser.setErrorHandler(new BailErrorStrategy());
}
protected void addStage2ErrorStategy(final UriParserParser parser) {
// Throw exception at first syntax error
parser.setErrorHandler(new BailErrorStrategy());
}
protected void addStage1ErrorListener(final UriParserParser parser) {
// No error logging to System.out or System.err, only exceptions used (depending on ErrorStrategy)
parser.removeErrorListeners();
parser.addErrorListener(new CheckFullContextListener());
}
protected void addStage2ErrorListener(final UriParserParser parser) {
// No error logging to System.out or System.err, only exceptions used (depending on ErrorStrategy)
parser.removeErrorListeners();
}
public void showTokens(final String input, final List<? extends Token> list) {
boolean first = true;
System.out.println("input: " + input);
String nL = "\n";
String out = "[" + nL;
for (Token token : list) {
if (!first) {
out += ",";
first = false;
}
int index = token.getType();
if (index != -1) {
out += "\"" + token.getText() + "\"" + " " + UriLexer.tokenNames[index] + nL;
} else {
out += "\"" + token.getText() + "\"" + " " + index + nL;
}
}
out += ']';
System.out.println("tokens: " + out);
return;
}
}