[OLINGO-568] Merge branch 'OLINGO-568_SearchParser_Draft'

This commit is contained in:
Michael Bolz 2015-11-17 15:31:44 +01:00
commit 1a59a5804b
21 changed files with 2211 additions and 28 deletions

View File

@ -21,10 +21,11 @@ package org.apache.olingo.fit.tecsvc.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import org.apache.olingo.client.api.communication.ODataClientErrorException;
import org.apache.olingo.client.api.communication.ODataServerErrorException;
import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest;
import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
import org.apache.olingo.client.api.domain.ClientEntity;
@ -301,18 +302,41 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase {
}
@Test
public void negativeSearch() {
public void basicSearch() {
ODataEntitySetRequest<ClientEntitySet> request = getClient().getRetrieveRequestFactory()
.getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI)
.appendEntitySetSegment(ES_ALL_PRIM)
.search("ABC")
.search("Second")
.build());
setCookieHeader(request);
try {
request.execute();
fail();
} catch (ODataServerErrorException e) {
assertEquals("HTTP/1.1 501 Not Implemented", e.getMessage());
}
ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities();
assertEquals(1, entities.size());
}
@Test
public void andSearch() {
ODataEntitySetRequest<ClientEntitySet> request = getClient().getRetrieveRequestFactory()
.getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI)
.appendEntitySetSegment(ES_ALL_PRIM)
.search("Second AND positive")
.build());
setCookieHeader(request);
ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities();
assertEquals(0, entities.size());
}
@Test
public void orSearch() {
ODataEntitySetRequest<ClientEntitySet> request = getClient().getRetrieveRequestFactory()
.getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI)
.appendEntitySetSegment(ES_ALL_PRIM)
.search("Second OR positive")
.build());
setCookieHeader(request);
ODataRetrieveResponse<ClientEntitySet> response = request.execute();
List<ClientEntity> entities = response.getBody().getEntities();
assertEquals(2, entities.size());
}
}

View File

@ -19,5 +19,17 @@
package org.apache.olingo.server.api.uri.queryoption.search;
public interface SearchExpression {
//No additional methods needed for now.
boolean isSearchTerm();
SearchTerm asSearchTerm();
boolean isSearchBinary();
SearchBinary asSearchBinary();
boolean isSearchUnary();
SearchUnary asSearchUnary();
}

View File

@ -18,8 +18,9 @@
*/
package org.apache.olingo.server.api.uri.queryoption.search;
public interface SearchUnary {
public interface SearchUnary extends SearchExpression {
SearchExpression getOperand();
SearchUnaryOperatorKind getOperator();
SearchTerm getOperand();
}

View File

@ -58,6 +58,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.MetadataEOFContex
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.parser.search.SearchParser;
import org.apache.olingo.server.core.uri.queryoption.AliasQueryOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.CustomQueryOptionImpl;
@ -80,7 +81,7 @@ public class Parser {
int logLevel = 0;
private enum ParserEntryRules {
All, Batch, CrossJoin, Entity, ExpandItems, FilterExpression, Metadata, PathSegment, Orderby, Select
All, Batch, CrossJoin, Entity, ExpandItems, FilterExpression, Metadata, PathSegment, Orderby, Select, Search
}
public Parser setLogLevel(final int logLevel) {
@ -218,8 +219,8 @@ public class Parser {
systemOption = (OrderByOptionImpl) uriParseTreeVisitor.visitOrderByEOF(ctxOrderByExpression);
} else if (option.name.equals(SystemQueryOptionKind.SEARCH.toString())) {
throw new UriParserSemanticException("System query option '$search' not implemented!",
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, "System query option '$search");
SearchParser searchParser = new SearchParser();
systemOption = searchParser.parse(option.value);
} else if (option.name.equals(SystemQueryOptionKind.SELECT.toString())) {
SelectEOFContext ctxSelectEOF =
(SelectEOFContext) parseRule(option.value, ParserEntryRules.Select);
@ -386,6 +387,9 @@ public class Parser {
case Select:
ret = parser.selectEOF();
break;
case Search:
ret = parser.searchInline();
break;
default:
break;
@ -443,6 +447,9 @@ public class Parser {
case Select:
ret = parser.selectEOF();
break;
case Search:
ret = parser.searchInline();
break;
default:
break;
}
@ -501,7 +508,7 @@ public class Parser {
} else {
out.append(index);
}
out.append(nL);
out.append(nL);
}
out.append(']');
System.out.println("tokens: " + out.toString());

View File

@ -0,0 +1,56 @@
/*
* 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.search;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinary;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
public class SearchBinaryImpl extends SearchExpressionImpl implements SearchBinary {
private final SearchBinaryOperatorKind operator;
private final SearchExpression left;
private final SearchExpression right;
public SearchBinaryImpl(SearchExpression left, SearchBinaryOperatorKind operator, SearchExpression right) {
this.left = left;
this.operator = operator;
this.right = right;
}
@Override
public SearchBinaryOperatorKind getOperator() {
return operator;
}
@Override
public SearchExpression getLeftOperand() {
return left;
}
@Override
public SearchExpression getRightOperand() {
return right;
}
@Override
public String toString() {
return "{" + left + " " + operator.name() + " " + right + '}';
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.search;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinary;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm;
import org.apache.olingo.server.api.uri.queryoption.search.SearchUnary;
public abstract class SearchExpressionImpl implements SearchExpression {
@Override
public boolean isSearchTerm() {
return this instanceof SearchTerm;
}
@Override
public SearchTerm asSearchTerm() {
return isSearchTerm() ? (SearchTerm) this : null;
}
@Override
public boolean isSearchBinary() {
return this instanceof SearchBinary;
}
@Override
public SearchBinary asSearchBinary() {
return isSearchBinary() ? (SearchBinary) this : null;
}
@Override
public boolean isSearchUnary() {
return this instanceof SearchUnary;
}
@Override
public SearchUnary asSearchUnary() {
return isSearchUnary() ? (SearchUnary) this : null;
}
}

View File

@ -0,0 +1,196 @@
/*
* 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.search;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm;
import org.apache.olingo.server.core.uri.parser.search.SearchQueryToken.Token;
import org.apache.olingo.server.core.uri.queryoption.SearchOptionImpl;
import java.util.Iterator;
import java.util.List;
public class SearchParser {
private Iterator<SearchQueryToken> tokens;
private SearchQueryToken token;
public SearchOption parse(String searchQuery) throws SearchParserException, SearchTokenizerException {
SearchTokenizer tokenizer = new SearchTokenizer();
SearchExpression searchExpression;
try {
searchExpression = parse(tokenizer.tokenize(searchQuery));
} catch (SearchTokenizerException e) {
return null;
}
final SearchOptionImpl searchOption = new SearchOptionImpl();
searchOption.setSearchExpression(searchExpression);
return searchOption;
}
protected SearchExpression parse(List<SearchQueryToken> tokens) throws SearchParserException {
this.tokens = tokens.iterator();
nextToken();
if (token == null) {
throw new SearchParserException("No search String", SearchParserException.MessageKeys.NO_EXPRESSION_FOUND);
}
return processSearchExpression(null);
}
private SearchExpression processSearchExpression(SearchExpression left) throws SearchParserException {
if (isEof()) {
return left;
}
if (left == null && (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OR))) {
throw new SearchParserException(token.getToken() + " needs a left operand.",
SearchParserException.MessageKeys.INVALID_BINARY_OPERATOR_POSITION, token.getToken().toString());
}
SearchExpression expression = left;
if (isToken(SearchQueryToken.Token.OPEN)) {
processOpen();
expression = processSearchExpression(left);
validateToken(SearchQueryToken.Token.CLOSE);
processClose();
} else if (isTerm()) {
expression = processTerm();
}
if (expression == null) {
throw new SearchParserException("Brackets must contain an expression.",
SearchParserException.MessageKeys.NO_EXPRESSION_FOUND);
}
if (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OPEN) || isTerm()) {
expression = processAnd(expression);
} else if (isToken(SearchQueryToken.Token.OR)) {
expression = processOr(expression);
} else if (isEof()) {
return expression;
}
return expression;
}
private boolean isTerm() {
return isToken(SearchQueryToken.Token.NOT)
|| isToken(SearchQueryToken.Token.PHRASE)
|| isToken(SearchQueryToken.Token.WORD);
}
private boolean isEof() {
return token == null;
}
private boolean isToken(SearchQueryToken.Token toCheckToken) {
return token != null && token.getToken() == toCheckToken;
}
private void validateToken(SearchQueryToken.Token toValidateToken) throws SearchParserException {
if (!isToken(toValidateToken)) {
String actualToken = token == null ? "null" : token.getToken().toString();
throw new SearchParserException("Expected " + toValidateToken + " but was " + actualToken,
SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, toValidateToken.toString(), actualToken);
}
}
private void processClose() {
nextToken();
}
private void processOpen() {
nextToken();
}
private SearchExpression processAnd(SearchExpression left) throws SearchParserException {
if (isToken(SearchQueryToken.Token.AND)) {
nextToken();
}
SearchExpression se = left;
if (isTerm()) {
se = processTerm();
se = new SearchBinaryImpl(left, SearchBinaryOperatorKind.AND, se);
return processSearchExpression(se);
} else {
if (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OR)) {
throw new SearchParserException("Operators must not be followed by an AND or an OR",
SearchParserException.MessageKeys.INVALID_OPERATOR_AFTER_AND, token.getToken().toString());
}
se = processSearchExpression(se);
return new SearchBinaryImpl(left, SearchBinaryOperatorKind.AND, se);
}
}
public SearchExpression processOr(SearchExpression left) throws SearchParserException {
if (isToken(SearchQueryToken.Token.OR)) {
nextToken();
}
SearchExpression se = processSearchExpression(left);
return new SearchBinaryImpl(left, SearchBinaryOperatorKind.OR, se);
}
private SearchExpression processNot() throws SearchParserException {
nextToken();
if (isToken(Token.WORD) || isToken(Token.PHRASE)) {
return new SearchUnaryImpl(processWordOrPhrase());
}
throw new SearchParserException("NOT must be followed by a term not a " + token.getToken(),
SearchParserException.MessageKeys.INVALID_NOT_OPERAND, token.getToken().toString());
}
private void nextToken() {
if (tokens.hasNext()) {
token = tokens.next();
} else {
token = null;
}
}
private SearchExpression processTerm() throws SearchParserException {
if (isToken(SearchQueryToken.Token.NOT)) {
return processNot();
}
return processWordOrPhrase();
}
private SearchTerm processWordOrPhrase() throws SearchParserException {
if (isToken(Token.PHRASE)) {
return processPhrase();
} else if (isToken(Token.WORD)) {
return processWord();
}
throw new SearchParserException("Expected PHRASE||WORD found: " + token.getToken(),
SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN,
Token.PHRASE.name() + "" + Token.WORD.name(), token.getToken().toString());
}
private SearchTerm processWord() {
String literal = token.getLiteral();
nextToken();
return new SearchTermImpl(literal);
}
private SearchTerm processPhrase() {
String literal = token.getLiteral();
nextToken();
return new SearchTermImpl(literal.substring(1,literal.length()-1));
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.search;
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
public class SearchParserException extends UriParserSyntaxException {
private static final long serialVersionUID = 5781553037561337795L;
public static enum MessageKeys implements MessageKey {
/** parameter: operatorkind */
INVALID_BINARY_OPERATOR_POSITION,
/** parameter: operatorkind */
INVALID_NOT_OPERAND,
/** parameters: expectedToken actualToken */
EXPECTED_DIFFERENT_TOKEN,
NO_EXPRESSION_FOUND,
/** parameter: operatorkind */
INVALID_OPERATOR_AFTER_AND;
@Override
public String getKey() {
return name();
}
}
public SearchParserException(final String developmentMessage, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, messageKey, parameters);
}
public SearchParserException(final String developmentMessage, final Throwable cause, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, cause, messageKey, parameters);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.search;
public interface SearchQueryToken {
enum Token {OPEN, NOT, AND, OR, WORD, PHRASE, CLOSE}
Token getToken();
String getLiteral();
}

View File

@ -0,0 +1,39 @@
/*
* 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.search;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm;
public class SearchTermImpl extends SearchExpressionImpl implements SearchTerm {
private final String term;
public SearchTermImpl(String term) {
this.term = term;
}
@Override
public String getSearchTerm() {
return term;
}
@Override
public String toString() {
return "'" + term + "'";
}
}

View File

@ -0,0 +1,571 @@
/*
* 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.search;
import java.util.ArrayList;
import java.util.List;
/**
* <code>
* searchExpr = ( OPEN BWS searchExpr BWS CLOSE
* / searchTerm
* ) [ searchOrExpr
* / searchAndExpr
* ]
*
* searchOrExpr = RWS 'OR' RWS searchExpr
* searchAndExpr = RWS [ 'AND' RWS ] searchExpr
*
* searchTerm = [ 'NOT' RWS ] ( searchPhrase / searchWord )
* searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark
* searchWord = 1*ALPHA ; Actually: any character from the Unicode categories L or Nl,
* ; but not the words AND, OR, and NOT
* </code>
*/
public class SearchTokenizer {
private static abstract class State implements SearchQueryToken {
private Token token = null;
private boolean finished = false;
protected static final char QUOTATION_MARK = '\"';
protected static final char CHAR_N = 'N';
protected static final char CHAR_O = 'O';
protected static final char CHAR_T = 'T';
protected static final char CHAR_A = 'A';
protected static final char CHAR_D = 'D';
protected static final char CHAR_R = 'R';
protected static final char CHAR_CLOSE = ')';
protected static final char CHAR_OPEN = '(';
public State(Token t) {
token = t;
}
protected abstract State nextChar(char c) throws SearchTokenizerException;
public State allowed(char c) {
return this;
}
public State forbidden(char c) throws SearchTokenizerException {
throw new SearchTokenizerException("Forbidden character for " + this.getClass().getName() + "->" + c,
SearchTokenizerException.MessageKeys.FORBIDDEN_CHARACTER, "" + c);
}
public State finish() {
this.finished = true;
return this;
}
public boolean isFinished() {
return finished;
}
public Token getToken() {
return token;
}
public State close() {
return this;
}
static boolean isAllowedWord(final char character) {
// TODO mibo: add missing allowed characters
int type = Character.getType(character);
return (type == Character.LETTER_NUMBER
|| type == Character.LOWERCASE_LETTER
|| type == Character.MODIFIER_LETTER
|| type == Character.OTHER_LETTER
|| type == Character.TITLECASE_LETTER
|| type == Character.UPPERCASE_LETTER);
}
/**
* searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark
*
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
*
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
*
* escape = "\" / "%5C" ; reverse solidus U+005C
*
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
*
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
*
* quotation-mark = DQUOTE / "%22"
*
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* DQUOTE = %x22
*
* @param character which is checked
* @return true if character is allowed for a phrase
*/
static boolean isAllowedPhrase(final char character) {
// FIXME mibo: check missing
return isQCharUnescaped(character) || isEscaped(character);
}
/**
* escape = "\" / "%5C" ; reverse solidus U+005C
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isEscaped(char character) {
// TODO: mibo(151117): check how to implement
return false;
}
/**
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isQCharUnescaped(char character) {
return isUnreserved(character)
|| isPctEncodedUnescaped(character)
|| isOtherDelims(character)
|| character == ':'
|| character == '@'
|| character == '/'
|| character == '$'
|| character == '\''
|| character == '=';
}
/**
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isOtherDelims(char character) {
return character == '!'
|| character == '('
|| character == ')'
|| character == '*'
|| character == '+'
|| character == ','
|| character == ';';
}
/**
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
*
* HEXDIG = DIGIT / A-to-F
*
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isPctEncodedUnescaped(char character) {
String hex = Integer.toHexString((int) character);
char aschar[] = hex.toCharArray();
if(aschar[0] == '%') {
if(aschar[1] == '2') {
return aschar[2] != '2' && isHexDigit(aschar[2]);
} else if(aschar[1] == '5') {
return aschar[2] != 'C' && isHexDigit(aschar[2]);
} else if(isHexDigit(aschar[1])) {
return isHexDigit(aschar[2]);
}
}
return false;
}
private static boolean isHexDigit(char character) {
return 'A' <= character && character <= 'F' // case A..F
|| '0' <= character && character <= '9'; // case 0..9
}
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isUnreserved(char character) {
return isAlphaOrDigit(character)
|| character == '-'
|| character == '.'
|| character == '_'
|| character == '~';
}
/**
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isAlphaOrDigit(char character) {
return 'A' <= character && character <= 'Z' // case A..Z
|| 'a' <= character && character <= 'z' // case a..z
|| '0' <= character && character <= '9'; // case 0..9
}
// BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace
// RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace
static boolean isWhitespace(final char character) {
// ( SP / HTAB / "%20" / "%09" )
// TODO mibo: add missing whitespaces
return character == ' ' || character == '\t';
}
@Override
public String getLiteral() {
return token.toString();
}
@Override
public String toString() {
return this.getToken().toString() + "=>{" + getLiteral() + "}";
}
}
private static abstract class LiteralState extends State {
protected final StringBuilder literal = new StringBuilder();
public LiteralState(Token t) {
super(t);
}
public LiteralState(Token t, char c) throws SearchTokenizerException {
super(t);
init(c);
}
public LiteralState(Token t, String initLiteral) {
super(t);
literal.append(initLiteral);
}
public State allowed(char c) {
literal.append(c);
return this;
}
@Override
public String getLiteral() {
return literal.toString();
}
public State init(char c) throws SearchTokenizerException {
if (isFinished()) {
throw new SearchTokenizerException(toString() + " is already finished.",
SearchTokenizerException.MessageKeys.ALREADY_FINISHED);
}
literal.append(c);
return this;
}
}
private class SearchExpressionState extends LiteralState {
public SearchExpressionState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (c == CHAR_OPEN) {
return new OpenState();
} else if (isWhitespace(c)) {
return new RwsState();
} else if (c == CHAR_CLOSE) {
return new CloseState();
} else {
return new SearchTermState().init(c);
}
}
@Override
public State init(char c) throws SearchTokenizerException {
return nextChar(c);
}
}
private class SearchTermState extends LiteralState {
public SearchTermState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (c == CHAR_N) {
return new NotState(c);
} else if (c == QUOTATION_MARK) {
return new SearchPhraseState(c);
} else if (isAllowedWord(c)) {
return new SearchWordState(c);
}
return forbidden(c);
}
@Override
public State init(char c) throws SearchTokenizerException {
return nextChar(c);
}
}
private class SearchWordState extends LiteralState {
public SearchWordState(char c) throws SearchTokenizerException {
super(Token.WORD, c);
if (!isAllowedWord(c)) {
forbidden(c);
}
}
public SearchWordState(State toConsume) throws SearchTokenizerException {
super(Token.WORD, toConsume.getLiteral());
char[] chars = literal.toString().toCharArray();
for (char aChar : chars) {
if (!isAllowedWord(aChar)) {
forbidden(aChar);
}
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isAllowedWord(c)) {
return allowed(c);
} else if (c == CHAR_CLOSE) {
finish();
return new CloseState();
} else if (isWhitespace(c)) {
finish();
return new RwsState();
}
return forbidden(c);
}
@Override
public State close() {
return finish();
}
}
private class SearchPhraseState extends LiteralState {
public SearchPhraseState(char c) throws SearchTokenizerException {
super(Token.PHRASE, c);
if (c != QUOTATION_MARK) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isAllowedPhrase(c)) {
return allowed(c);
} else if (isWhitespace(c)) {
return allowed(c);
} else if (c == QUOTATION_MARK) {
finish();
allowed(c);
return new SearchExpressionState();
} else if (isFinished()) {
return new SearchExpressionState().init(c);
}
return forbidden(c);
}
}
private class OpenState extends State {
public OpenState() {
super(Token.OPEN);
finish();
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
finish();
if (isWhitespace(c)) {
return forbidden(c);
}
return new SearchExpressionState().init(c);
}
}
private class CloseState extends State {
public CloseState() {
super(Token.CLOSE);
finish();
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
return new SearchExpressionState().init(c);
}
}
private class NotState extends LiteralState {
public NotState(char c) throws SearchTokenizerException {
super(Token.NOT, c);
if (c != CHAR_N) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && c == CHAR_O) {
return allowed(c);
} else if (literal.length() == 2 && c == CHAR_T) {
return allowed(c);
} else if (literal.length() == 3 && isWhitespace(c)) {
finish();
return new BeforePhraseOrWordRwsState();
}
return forbidden(c);
}
}
private class AndState extends LiteralState {
public AndState(char c) throws SearchTokenizerException {
super(Token.AND, c);
if (c != CHAR_A) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && c == CHAR_N) {
return allowed(c);
} else if (literal.length() == 2 && c == CHAR_D) {
return allowed(c);
} else if (literal.length() == 3 && isWhitespace(c)) {
finish();
return new BeforeSearchExpressionRwsState();
} else {
return new SearchWordState(this);
}
}
}
private class OrState extends LiteralState {
public OrState(char c) throws SearchTokenizerException {
super(Token.OR, c);
if (c != CHAR_O) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && (c == CHAR_R)) {
return allowed(c);
} else if (literal.length() == 2 && isWhitespace(c)) {
finish();
return new BeforeSearchExpressionRwsState();
} else {
return new SearchWordState(this);
}
}
}
// RWS 'OR' RWS searchExpr
// RWS [ 'AND' RWS ] searchExpr
private class BeforeSearchExpressionRwsState extends State {
public BeforeSearchExpressionRwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
return allowed(c);
} else {
return new SearchExpressionState().init(c);
}
}
}
private class BeforePhraseOrWordRwsState extends State {
public BeforePhraseOrWordRwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
return allowed(c);
} else if (c == '"') {
return new SearchPhraseState(c);
} else {
return new SearchWordState(c);
}
}
}
private class RwsState extends State {
public RwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
return allowed(c);
} else if (c == CHAR_O) {
return new OrState(c);
} else if (c == CHAR_A) {
return new AndState(c);
} else {
return new SearchExpressionState().init(c);
}
}
}
/**
* Take the search query and split into according SearchQueryToken.
* Before split into tokens the given search query is 'trimmed'.
*
* @param searchQuery search query to be tokenized
* @return list of tokens
* @throws SearchTokenizerException if something in query is not valid
* (based on OData search query ABNF)
*/
public List<SearchQueryToken> tokenize(final String searchQuery)
throws SearchTokenizerException {
char[] chars = searchQuery.trim().toCharArray();
State state = new SearchExpressionState();
List<SearchQueryToken> states = new ArrayList<SearchQueryToken>();
for (char aChar : chars) {
State next = state.nextChar(aChar);
if (state.isFinished()) {
states.add(state);
}
state = next;
}
if (state.close().isFinished()) {
states.add(state);
}
return states;
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.search;
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
public class SearchTokenizerException extends UriParserSyntaxException {
private static final long serialVersionUID = -8295456415309640166L;
public static enum MessageKeys implements MessageKey {
/** parameter: character */
FORBIDDEN_CHARACTER,
ALREADY_FINISHED;
@Override
public String getKey() {
return name();
}
}
public SearchTokenizerException(final String developmentMessage, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, messageKey, parameters);
}
public SearchTokenizerException(final String developmentMessage, final Throwable cause, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, cause, messageKey, parameters);
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.search;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm;
import org.apache.olingo.server.api.uri.queryoption.search.SearchUnary;
import org.apache.olingo.server.api.uri.queryoption.search.SearchUnaryOperatorKind;
public class SearchUnaryImpl extends SearchExpressionImpl implements SearchUnary {
private final SearchTerm operand;
public SearchUnaryImpl(SearchTerm operand) {
this.operand = operand;
}
@Override
public SearchUnaryOperatorKind getOperator() {
return SearchUnaryOperatorKind.NOT;
}
@Override
public SearchTerm getOperand() {
return operand;
}
@Override
public String toString() {
return "{" + getOperator().name() + " " + operand + '}';
}
}

View File

@ -24,13 +24,18 @@ import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
public class SearchOptionImpl extends SystemQueryOptionImpl implements SearchOption {
private SearchExpression searchExpression;
public SearchOptionImpl() {
setKind(SystemQueryOptionKind.SEARCH);
}
@Override
public SearchExpression getSearchExpression() {
return null;
return searchExpression;
}
public void setSearchExpression(SearchExpression searchExpression) {
this.searchExpression = searchExpression;
}
}

View File

@ -0,0 +1,198 @@
/*
* 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.search;
import static org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind.AND;
import static org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind.OR;
import java.lang.reflect.Field;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchUnary;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
public class SearchParserAndTokenizerTest {
@Test
public void basicParsing() throws Exception {
SearchExpressionValidator.init("a")
.validate(with("a"));
SearchExpressionValidator.init("a AND b")
.validate(with("a", and("b")));
SearchExpressionValidator.init("a AND b AND c")
.validate("{{'a' AND 'b'} AND 'c'}");
SearchExpressionValidator.init("a OR b")
.validate(with("a", or("b")));
SearchExpressionValidator.init("a OR b OR c")
.validate(with("a", or("b", or("c"))));
}
@Test
public void mixedParsing() throws Exception {
SearchExpressionValidator.init("a AND b OR c")
.validate("{{'a' AND 'b'} OR 'c'}");
SearchExpressionValidator.init("a OR b AND c")
.validate("{'a' OR {'b' AND 'c'}}");
}
@Test
public void notParsing() throws Exception {
SearchExpressionValidator.init("NOT a AND b OR c")
.validate("{{{NOT 'a'} AND 'b'} OR 'c'}");
SearchExpressionValidator.init("a OR b AND NOT c")
.validate("{'a' OR {'b' AND {NOT 'c'}}}");
}
@Test
public void parenthesesParsing() throws Exception {
SearchExpressionValidator.init("a AND (b OR c)")
.validate("{'a' AND {'b' OR 'c'}}");
SearchExpressionValidator.init("(a OR b) AND NOT c")
.validate("{{'a' OR 'b'} AND {NOT 'c'}}");
}
@Ignore
@Test
public void sebuilder() {
System.out.println(with("c", or("a", and("b"))).toString());
System.out.println(with("a", and("b", and("c"))).toString());
System.out.println(with("a").toString());
System.out.println(with(not("a")).toString());
System.out.println(with("a", and("b")).toString());
System.out.println(with("a", or("b")).toString());
System.out.println(with("a", and(not("b"))).toString());
}
private static SearchExpression with(String term) {
return new SearchTermImpl(term);
}
private static SearchExpression with(String left, SearchExpression right) {
setLeftField(left, right);
return right;
}
private static SearchUnary with(SearchUnary unary) {
return unary;
}
private static SearchExpression or(String left, SearchExpression right) {
SearchExpression or = or(right);
setLeftField(left, right);
return or;
}
private static SearchExpression and(String left, SearchExpression right) {
SearchExpression and = and(right);
setLeftField(left, right);
return and;
}
private static SearchExpression or(SearchExpression right) {
return new SearchBinaryImpl(null, OR, right);
}
private static SearchExpression and(SearchExpression right) {
return new SearchBinaryImpl(null, AND, right);
}
private static SearchExpression and(String right) {
return and(new SearchTermImpl(right));
}
private static SearchExpression or(String right) {
return or(new SearchTermImpl(right));
}
private static SearchUnary not(String term) {
return new SearchUnaryImpl(new SearchTermImpl(term));
}
private static void setLeftField(String left, SearchExpression se) {
try {
Field field = null;
if (se instanceof SearchUnaryImpl) {
field = SearchBinaryImpl.class.getDeclaredField("operand");
} else if (se instanceof SearchBinaryImpl) {
field = SearchBinaryImpl.class.getDeclaredField("left");
} else {
Assert.fail("Unexpected exception: " + se.getClass());
}
field.setAccessible(true);
field.set(se, new SearchTermImpl(left));
} catch (Exception e) {
Assert.fail("Unexpected exception: " + e.getClass());
}
}
private static class SearchExpressionValidator {
private boolean log;
private final String searchQuery;
private SearchExpressionValidator(String searchQuery) {
this.searchQuery = searchQuery;
}
private static SearchExpressionValidator init(String searchQuery) {
return new SearchExpressionValidator(searchQuery);
}
@SuppressWarnings("unused")
private SearchExpressionValidator enableLogging() {
log = true;
return this;
}
private void validate(Class<? extends Exception> exception) throws SearchTokenizerException {
try {
new SearchTokenizer().tokenize(searchQuery);
} catch (Exception e) {
Assert.assertEquals(exception, e.getClass());
return;
}
Assert.fail("Expected exception " + exception.getClass().getSimpleName() + " was not thrown.");
}
private void validate(SearchExpression expectedSearchExpression) throws SearchTokenizerException,
SearchParserException {
final SearchExpression searchExpression = getSearchExpression();
Assert.assertEquals(expectedSearchExpression.toString(), searchExpression.toString());
}
private void validate(String expectedSearchExpression) throws SearchTokenizerException, SearchParserException {
final SearchExpression searchExpression = getSearchExpression();
Assert.assertEquals(expectedSearchExpression, searchExpression.toString());
}
private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
SearchParser tokenizer = new SearchParser();
SearchOption result = tokenizer.parse(searchQuery);
Assert.assertNotNull(result);
final SearchExpression searchExpression = result.getSearchExpression();
Assert.assertNotNull(searchExpression);
if (log) {
System.out.println(searchExpression);
}
return searchExpression;
}
}
}

View File

@ -0,0 +1,248 @@
/*
* 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.search;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.core.uri.parser.search.SearchParserException.MessageKeys;
import org.apache.olingo.server.core.uri.parser.search.SearchQueryToken.Token;
import org.junit.Test;
public class SearchParserTest extends SearchParser {
@Test
public void simple() throws Exception {
SearchExpression se = run(Token.WORD);
assertEquals("'word1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("word1", se.asSearchTerm().getSearchTerm());
se = run(Token.PHRASE);
assertEquals("'phrase1'", se.toString());
assertTrue(se.isSearchTerm());
// TODO: Check if quotation marks should be part of the string we deliver
assertEquals("phrase1", se.asSearchTerm().getSearchTerm());
}
@Test
public void simpleAnd() throws Exception {
SearchExpression se = run(Token.WORD, Token.AND, Token.WORD);
assertEquals("{'word1' AND 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.AND, Token.PHRASE);
assertEquals("{'phrase1' AND 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleOr() throws Exception {
SearchExpression se = run(Token.WORD, Token.OR, Token.WORD);
assertEquals("{'word1' OR 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.OR, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.OR, Token.PHRASE);
assertEquals("{'phrase1' OR 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.OR, se.asSearchBinary().getOperator());
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleImplicitAnd() throws Exception {
SearchExpression se = run(Token.WORD, Token.WORD);
assertEquals("{'word1' AND 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.PHRASE);
assertEquals("{'phrase1' AND 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleBrackets() throws Exception {
SearchExpression se = run(Token.OPEN, Token.WORD, Token.CLOSE);
assertEquals("'word1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("word1", se.asSearchTerm().getSearchTerm());
se = run(Token.OPEN, Token.PHRASE, Token.CLOSE);
assertEquals("'phrase1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("phrase1", se.asSearchTerm().getSearchTerm());
}
@Test
public void simpleNot() throws Exception {
SearchExpression se = run(Token.NOT, Token.WORD);
assertEquals("{NOT 'word1'}", se.toString());
assertTrue(se.isSearchUnary());
assertEquals("word1", se.asSearchUnary().getOperand().asSearchTerm().getSearchTerm());
se = run(Token.NOT, Token.PHRASE);
assertEquals("{NOT 'phrase1'}", se.toString());
assertTrue(se.isSearchUnary());
assertEquals("phrase1", se.asSearchUnary().getOperand().asSearchTerm().getSearchTerm());
}
@Test
public void precedenceLast() throws Exception {
// word1 AND (word2 AND word3)
SearchExpression se = run(Token.WORD, Token.AND, Token.OPEN, Token.WORD, Token.AND, Token.WORD, Token.CLOSE);
assertEquals("{'word1' AND {'word2' AND 'word3'}}", se.toString());
}
@Test
public void precedenceFirst() throws Exception {
// (word1 AND word2) AND word3
SearchExpression se = run(Token.OPEN, Token.WORD, Token.AND, Token.WORD, Token.CLOSE, Token.AND, Token.WORD);
assertEquals("{{'word1' AND 'word2'} AND 'word3'}", se.toString());
}
@Test
public void combinationAndOr() throws Exception {
// word1 AND word2 OR word3
SearchExpression se = run(Token.WORD, Token.AND, Token.WORD, Token.OR, Token.WORD);
assertEquals("{{'word1' AND 'word2'} OR 'word3'}", se.toString());
// word1 OR word2 AND word3
se = run(Token.WORD, Token.OR, Token.WORD, Token.AND, Token.WORD);
assertEquals("{'word1' OR {'word2' AND 'word3'}}", se.toString());
}
@Test
public void unnecessaryBrackets() throws Exception {
// (word1) (word2)
SearchExpression se = run(Token.OPEN, Token.WORD, Token.CLOSE, Token.OPEN, Token.WORD, Token.CLOSE);
assertEquals("{'word1' AND 'word2'}", se.toString());
}
@Test
public void complex() throws Exception {
// ((word1 word2) word3) OR word4
SearchExpression se =
run(Token.OPEN, Token.OPEN, Token.WORD, Token.WORD, Token.CLOSE, Token.WORD, Token.CLOSE, Token.OR, Token.WORD);
assertEquals("{{{'word1' AND 'word2'} AND 'word3'} OR 'word4'}", se.toString());
}
@Test
public void doubleNot() throws Exception {
SearchExpression se = run(Token.NOT, Token.WORD, Token.AND, Token.NOT, Token.PHRASE);
assertEquals("{{NOT 'word1'} AND {NOT 'phrase1'}}", se.toString());
}
@Test
public void notAnd() throws Exception {
runEx(SearchParserException.MessageKeys.INVALID_NOT_OPERAND, Token.NOT, Token.AND);
}
@Test
public void notNotWord() throws Exception {
runEx(SearchParserException.MessageKeys.INVALID_NOT_OPERAND, Token.NOT, Token.NOT, Token.WORD);
}
@Test
public void doubleAnd() throws Exception {
runEx(SearchParserException.MessageKeys.INVALID_OPERATOR_AFTER_AND, Token.WORD, Token.AND, Token.AND, Token.WORD);
}
@Test
public void singleAnd() {
runEx(SearchParserException.MessageKeys.INVALID_BINARY_OPERATOR_POSITION, Token.AND);
}
@Test
public void singleOpenBracket() {
runEx(SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, Token.OPEN);
}
@Test
public void emptyBrackets() {
runEx(SearchParserException.MessageKeys.NO_EXPRESSION_FOUND, Token.OPEN, Token.CLOSE);
}
@Test
public void empty() {
Token[] emptyArray = new Token[0];
runEx(SearchParserException.MessageKeys.NO_EXPRESSION_FOUND, emptyArray);
}
private void runEx(MessageKeys key, Token... tokenArray) {
try {
run(tokenArray);
fail("Expected UriParserSyntaxException with key " + key);
} catch (SearchParserException e) {
assertEquals(key, e.getMessageKey());
}
}
private SearchExpression run(SearchQueryToken.Token... tokenArray) throws SearchParserException {
List<SearchQueryToken> tokenList = prepareTokens(tokenArray);
SearchExpression se = parse(tokenList);
assertNotNull(se);
return se;
}
public List<SearchQueryToken> prepareTokens(SearchQueryToken.Token... tokenArray) {
ArrayList<SearchQueryToken> tokenList = new ArrayList<SearchQueryToken>();
int wordNumber = 1;
int phraseNumber = 1;
for (Token aTokenArray : tokenArray) {
SearchQueryToken token = mock(SearchQueryToken.class);
when(token.getToken()).thenReturn(aTokenArray);
if (aTokenArray == Token.WORD) {
when(token.getLiteral()).thenReturn("word" + wordNumber);
wordNumber++;
} else if (aTokenArray == Token.PHRASE) {
when(token.getLiteral()).thenReturn("\"phrase" + phraseNumber + "\"");
phraseNumber++;
}
when(token.toString()).thenReturn("" + aTokenArray);
tokenList.add(token);
}
return tokenList;
}
}

View File

@ -0,0 +1,500 @@
/*
* 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.search;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.apache.olingo.server.core.uri.parser.search.SearchQueryToken.Token.*;
public class SearchTokenizerTest {
@Test
public void parseBasics() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
//
result = tokenizer.tokenize("abc");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
result = tokenizer.tokenize("NOT abc");
Assert.assertNotNull(result);
Assert.assertEquals(NOT, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(1).getToken());
result = tokenizer.tokenize("(abc)");
Assert.assertNotNull(result);
Assert.assertEquals(OPEN, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(1).getToken());
Assert.assertEquals(CLOSE, result.get(2).getToken());
result = tokenizer.tokenize("((abc))");
Assert.assertNotNull(result);
Assert.assertEquals(OPEN, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
Assert.assertEquals(CLOSE, result.get(4).getToken());
}
@Test
public void parseWords() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
//
result = tokenizer.tokenize("abc");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
//
result = tokenizer.tokenize("anotherWord\u1234");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
}
@Test
public void parsePhrase() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
SearchValidator.init("abc AND \"x-y_z\" AND olingo").validate();
//
result = tokenizer.tokenize("\"abc\"");
Assert.assertNotNull(result);
Assert.assertEquals(PHRASE, result.get(0).getToken());
//
result = tokenizer.tokenize("\"9988 abs\"");
Assert.assertNotNull(result);
Assert.assertEquals(PHRASE, result.get(0).getToken());
Assert.assertEquals("\"9988 abs\"", result.get(0).getLiteral());
//
result = tokenizer.tokenize("\"99_88.\"");
Assert.assertNotNull(result);
Assert.assertEquals(PHRASE, result.get(0).getToken());
Assert.assertEquals("\"99_88.\"", result.get(0).getLiteral());
SearchValidator.init("abc or \"xyz\"").addExpected(WORD, WORD, PHRASE).validate();
}
/**
* https://tools.oasis-open.org/version-control/browse/wsvn/odata/trunk/spec/ABNF/odata-abnf-testcases.xml
* @throws Exception
*/
@Test
@Ignore("Test must be moved to SearchParserTest and SearchParserAndTokenizerTest")
public void parsePhraseAbnfTestcases() throws Exception {
// <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions">
SearchValidator.init("\"blue%20green\"").validate();
// <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions">
SearchValidator.init("\"blue%20green%22").validate();
// <TestCase Name="5.1.7 Search - phrase with escaped double-quote" Rule="queryOptions">
// <Input>$search="blue\"green"</Input>
SearchValidator.init("\"blue\\\"green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with escaped backslash" Rule="queryOptions">
// <Input>$search="blue\\green"</Input>
SearchValidator.init("\"blue\\\\green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="14">
SearchValidator.init("\"blue\"green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="16">
SearchValidator.init("\"blue%22green\"").validate();
// <TestCase Name="5.1.7 Search - implicit AND" Rule="queryOptions">
// <Input>$search=blue green</Input>
// SearchValidator.init("\"blue%20green\"").validate();
// <TestCase Name="5.1.7 Search - implicit AND, encoced" Rule="queryOptions">
// SearchValidator.init("blue%20green").validate();
}
@Test
public void parseNot() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
result = tokenizer.tokenize("NOT abc");
Assert.assertNotNull(result);
Assert.assertEquals(NOT, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(1).getToken());
SearchValidator.init("not abc").addExpected(WORD, WORD).validate();
SearchValidator.init("NOT abc").addExpected(NOT, WORD).validate();
SearchValidator.init("NOT \"abc\"").addExpected(NOT, PHRASE).validate();
SearchValidator.init("NOT (sdf)").validate(SearchTokenizerException.class);
}
@Test
public void parseOr() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
result = tokenizer.tokenize("abc OR xyz");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(OR, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
result = tokenizer.tokenize("abc OR xyz OR olingo");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(OR, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
Assert.assertEquals(OR, result.get(3).getToken());
Assert.assertEquals(WORD, result.get(4).getToken());
SearchValidator.init("abc or xyz").addExpected(WORD, WORD, WORD).validate();
}
@Test
public void parseImplicitAnd() throws SearchTokenizerException {
SearchValidator.init("a b").addExpected(WORD, WORD).validate();
SearchValidator.init("a b OR c").addExpected(WORD, WORD, OR, WORD).validate();
SearchValidator.init("a bc OR c").addExpected(WORD, WORD, OR, WORD).validate();
SearchValidator.init("a bc c").addExpected(WORD, WORD, WORD).validate();
SearchValidator.init("(a OR x) bc c").addExpected(OPEN, WORD, OR, WORD, CLOSE, WORD, WORD).validate();
}
@Test
public void parseAnd() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
result = tokenizer.tokenize("abc AND xyz");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(AND, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
// no lower case allowed for AND
result = tokenizer.tokenize("abc and xyz");
Assert.assertNotNull(result);
Assert.assertEquals(3, result.size());
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
// implicit AND
result = tokenizer.tokenize("abc xyz");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(WORD, result.get(1).getToken());
result = tokenizer.tokenize("abc AND xyz AND olingo");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(AND, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
Assert.assertEquals(AND, result.get(3).getToken());
Assert.assertEquals(WORD, result.get(4).getToken());
result = tokenizer.tokenize("abc AND \"x-y_z\" AND olingo");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(AND, result.get(1).getToken());
Assert.assertEquals(PHRASE, result.get(2).getToken());
Assert.assertEquals("\"x-y_z\"", result.get(2).getLiteral());
Assert.assertEquals(AND, result.get(3).getToken());
Assert.assertEquals(WORD, result.get(4).getToken());
}
@Test
public void parseAndOr() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
result = tokenizer.tokenize("abc AND xyz OR olingo");
Assert.assertNotNull(result);
Assert.assertEquals(WORD, result.get(0).getToken());
Assert.assertEquals(AND, result.get(1).getToken());
Assert.assertEquals(WORD, result.get(2).getToken());
Assert.assertEquals(OR, result.get(3).getToken());
Assert.assertEquals(WORD, result.get(4).getToken());
SearchValidator.init("abc AND ANDsomething")
.addExpected(WORD, AND, WORD).validate();
}
@Test
public void parseCombinations() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
result = tokenizer.tokenize("abc AND NOT xyz OR olingo");
Assert.assertNotNull(result);
Iterator<SearchQueryToken> it = result.iterator();
Assert.assertEquals(WORD, it.next().getToken());
Assert.assertEquals(AND, it.next().getToken());
Assert.assertEquals(NOT, it.next().getToken());
Assert.assertEquals(WORD, it.next().getToken());
Assert.assertEquals(OR, it.next().getToken());
Assert.assertEquals(WORD, it.next().getToken());
SearchValidator.init("foo AND bar OR foo AND baz OR that AND bar OR that AND baz")
.addExpected(WORD, "foo").addExpected(AND)
.addExpected(WORD, "bar").addExpected(OR)
.addExpected(WORD, "foo").addExpected(AND)
.addExpected(WORD, "baz").addExpected(OR)
.addExpected(WORD, "that").addExpected(AND)
.addExpected(WORD, "bar").addExpected(OR)
.addExpected(WORD, "that").addExpected(AND)
.addExpected(WORD, "baz")
.validate();
SearchValidator.init("(foo OR that) AND (bar OR baz)")
.addExpected(OPEN)
.addExpected(WORD, "foo").addExpected(OR).addExpected(WORD, "that")
.addExpected(CLOSE).addExpected(AND).addExpected(OPEN)
.addExpected(WORD, "bar").addExpected(OR).addExpected(WORD, "baz")
.addExpected(CLOSE)
.validate();
}
@Test
public void parseSpecial() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result;
Iterator<SearchQueryToken> it;
result = tokenizer.tokenize("NOT abc AND nothing");
it = result.iterator();
Assert.assertEquals(NOT, it.next().getToken());
Assert.assertEquals(WORD, it.next().getToken());
Assert.assertEquals(AND, it.next().getToken());
Assert.assertEquals(WORD, it.next().getToken());
result = tokenizer.tokenize("abc AND andsomething");
it = result.iterator();
Assert.assertEquals(WORD, it.next().getToken());
Assert.assertEquals(AND, it.next().getToken());
Assert.assertEquals(WORD, it.next().getToken());
SearchValidator.init("abc AND ANDsomething")
.addExpected(WORD, AND, WORD).validate();
SearchValidator.init("abc ANDsomething")
.addExpected(WORD, WORD).validate();
SearchValidator.init("abc ORsomething")
.addExpected(WORD, WORD).validate();
SearchValidator.init("abc OR orsomething")
.addExpected(WORD, OR, WORD).validate();
SearchValidator.init("abc OR ORsomething")
.addExpected(WORD, OR, WORD).validate();
}
@Test
public void unicodeInWords() throws Exception {
// Ll, Lm, Lo, Lt, Lu, Nl
SearchValidator.init("abc OR Ll\u01E3Lm\u02B5Lo\u1BE4Lt\u01F2Lu\u03D3Nl\u216F")
.addExpected(WORD, OR, WORD).validate();
}
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
*
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
*
* escape = "\" / "%5C" ; reverse solidus U+005C
* quotation-mark = DQUOTE / "%22"
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* DQUOTE = %x22
*
* @throws Exception
*/
@Test
public void characterInPhrase() throws Exception {
SearchValidator.init("\"123\" OR \"ALPHA-._~\"")
.addExpected(PHRASE, OR, PHRASE).validate();
}
@Test
public void moreMixedTests() throws SearchTokenizerException {
validate("abc");
validate("NOT abc");
validate("abc AND def");
validate("abc OR def");
validate("abc def", WORD, WORD);
validate("abc AND def AND ghi", WORD, AND, WORD, AND, WORD);
validate("abc AND def OR ghi");
validate("abc AND def ghi");
validate("abc OR def AND ghi", WORD, OR, WORD, AND, WORD);
validate("abc OR def OR ghi", WORD, OR, WORD, OR, WORD);
validate("abc OR def ghi", WORD, OR, WORD, WORD);
validate("abc def AND ghi");
validate("abc def OR ghi");
validate("abc def ghi");
// mixed not
SearchValidator.init(" abc def AND ghi").validate(WORD, WORD, AND, WORD);
validate("NOT abc NOT def OR NOT ghi", NOT, WORD, NOT, WORD, OR, NOT, WORD);
validate(" abc def NOT ghi", WORD, WORD, NOT, WORD);
// parenthesis
validate("(abc)", OPEN, WORD, CLOSE);
validate("(abc AND def)", OPEN, WORD, AND, WORD, CLOSE);
validate("(abc AND def) OR ghi", OPEN, WORD, AND, WORD, CLOSE, OR, WORD);
validate("(abc AND def) ghi", OPEN, WORD, AND, WORD, CLOSE, WORD);
validate("abc AND (def OR ghi)", WORD, AND, OPEN, WORD, OR, WORD, CLOSE);
validate("abc AND (def ghi)", WORD, AND, OPEN, WORD, WORD, CLOSE);
}
@Test
public void parseInvalid() throws SearchTokenizerException {
SearchValidator.init("abc AND OR something").validate();
SearchValidator.init("abc AND \"something\" )").validate();
//
SearchValidator.init("( abc AND) OR something").validate(SearchTokenizerException.class);
}
public void validate(String query) throws SearchTokenizerException {
new SearchValidator(query).validate();
}
public void validate(String query, SearchQueryToken.Token ... tokens) throws SearchTokenizerException {
SearchValidator sv = new SearchValidator(query);
for (SearchQueryToken.Token token : tokens) {
sv.addExpected(token);
}
sv.validate();
}
private static class SearchValidator {
private List<Tuple> validations = new ArrayList<Tuple>();
private boolean log;
private final String searchQuery;
public void validate(SearchQueryToken.Token... tokens) throws SearchTokenizerException {
addExpected(tokens);
validate();
}
private class Tuple {
final SearchQueryToken.Token token;
final String literal;
public Tuple(SearchQueryToken.Token token, String literal) {
this.token = token;
this.literal = literal;
}
public Tuple(SearchQueryToken.Token token) {
this(token, null);
}
}
private SearchValidator(String searchQuery) {
this.searchQuery = searchQuery;
}
private static SearchValidator init(String searchQuery) {
return new SearchValidator(searchQuery);
}
@SuppressWarnings("unused")
private SearchValidator enableLogging() {
log = true;
return this;
}
private SearchValidator addExpected(SearchQueryToken.Token token, String literal) {
validations.add(new Tuple(token, literal));
return this;
}
private SearchValidator addExpected(SearchQueryToken.Token ... token) {
for (SearchQueryToken.Token t : token) {
validations.add(new Tuple(t));
}
return this;
}
private void validate(Class<? extends Exception> exception) throws SearchTokenizerException {
try {
new SearchTokenizer().tokenize(searchQuery);
} catch (Exception e) {
Assert.assertEquals(exception, e.getClass());
return;
}
Assert.fail("Expected exception " + exception.getClass().getSimpleName() + " was not thrown.");
}
private void validate() throws SearchTokenizerException {
SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result = tokenizer.tokenize(searchQuery);
Assert.assertNotNull(result);
if(log) {
System.out.println(result);
}
if(validations.size() != 0) {
Assert.assertEquals(validations.size(), result.size());
Iterator<Tuple> validationIt = validations.iterator();
for (SearchQueryToken iToken : result) {
Tuple validation = validationIt.next();
Assert.assertEquals(validation.token, iToken.getToken());
if(validation.literal != null) {
Assert.assertEquals(validation.literal, iToken.getLiteral());
}
}
}
}
}
}

View File

@ -67,6 +67,7 @@ import org.apache.olingo.server.tecsvc.processor.queryoptions.ExpandSystemQueryO
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.CountHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.FilterHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.OrderByHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.SearchHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.ServerSidePagingHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.SkipHandler;
import org.apache.olingo.server.tecsvc.processor.queryoptions.options.TopHandler;
@ -486,6 +487,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
OrderByHandler.applyOrderByOption(uriInfo.getOrderByOption(), entitySet, uriInfo, serviceMetadata.getEdm());
SkipHandler.applySkipSystemQueryHandler(uriInfo.getSkipOption(), entitySet);
TopHandler.applyTopSystemQueryOption(uriInfo.getTopOption(), entitySet);
SearchHandler.applySearchSystemQueryOption(uriInfo.getSearchOption(), entitySet);
final Integer pageSize = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getMaxPageSize();
final Integer serverPageSize = ServerSidePagingHandler.applyServerSidePaging(uriInfo.getSkipTokenOption(),

View File

@ -233,8 +233,7 @@ public abstract class TechnicalProcessor implements Processor {
}
protected void validateOptions(final UriInfoResource uriInfo) throws ODataApplicationException {
if (uriInfo.getIdOption() != null
|| uriInfo.getSearchOption() != null) {
if (uriInfo.getIdOption() != null) {
throw new ODataApplicationException("Not all of the specified options are supported.",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
}

View File

@ -0,0 +1,93 @@
/*
* 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.tecsvc.processor.queryoptions.options;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinary;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
public class SearchHandler {
public static void applySearchSystemQueryOption(final SearchOption searchOption, final EntityCollection entitySet)
throws ODataApplicationException {
if (searchOption != null) {
Iterator<Entity> it = entitySet.getEntities().iterator();
while(it.hasNext()) {
boolean keep = false;
Entity next = it.next();
List<Property> propertyList = next.getProperties();
for (Property property : propertyList) {
SearchExpression se = searchOption.getSearchExpression();
if(isTrue(se, property)) {
keep = true;
break;
}
}
if(!keep) {
it.remove();
}
}
}
}
private static boolean isTrue(SearchTerm term, Property property) {
if(property.isPrimitive() && !property.isNull()) {
// TODO: mibo(151117): pass EDM information to do correct 'string' convertation
String propertyString = property.asPrimitive().toString();
return propertyString != null && propertyString.contains(term.getSearchTerm());
}
return false;
}
private static boolean isTrue(SearchBinary binary, Property property) throws ODataApplicationException {
SearchExpression left = binary.getLeftOperand();
SearchExpression right = binary.getRightOperand();
if(binary.getOperator() == SearchBinaryOperatorKind.AND) {
return isTrue(left, property) && isTrue(right, property);
} else if(binary.getOperator() == SearchBinaryOperatorKind.OR) {
return isTrue(left, property) || isTrue(right, property);
} else {
throw new ODataApplicationException("Found unknown SearchBinaryOperatorKind: " + binary.getOperator(),
HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT);
}
}
private static boolean isTrue(SearchExpression searchExpression, Property property) throws ODataApplicationException {
if(searchExpression.isSearchBinary()) {
return isTrue(searchExpression.asSearchBinary(), property);
} else if(searchExpression.isSearchTerm()) {
return isTrue(searchExpression.asSearchTerm(), property);
} else if(searchExpression.isSearchUnary()) {
return !isTrue(searchExpression.asSearchUnary().getOperand(), property);
}
throw new ODataApplicationException("Found unknown SearchExpression: " + searchExpression,
HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT);
}
}

View File

@ -1193,9 +1193,10 @@ public class TestFullResourcePath {
.isExValidation(UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED);
// $search is currently not implemented. Please change this exception if the implementation is done.
testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='1')", "$search=test")
.isExSemantic(MessageKeys.NOT_IMPLEMENTED);
// FIXME (151106:mibo): check after finish of OLINGO-568
// testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='1')", "$search=test")
// .isExSemantic(MessageKeys.NOT_IMPLEMENTED);
testUri.run("ESAllPrim/olingo.odata.test1.BFNESAllPrimRTCTAllPrim()")
.isKind(UriInfoKind.resource)
.goPath().first()
@ -5455,12 +5456,12 @@ public class TestFullResourcePath {
testUri.run("ESTwoKeyNav", "$search= abc def NOT ghi");
// parenthesis
testUri.run("ESTwoKeyNav", "$search= (abc)");
testUri.run("ESTwoKeyNav", "$search= (abc AND def)");
testUri.run("ESTwoKeyNav", "$search= (abc AND def) OR ghi ");
testUri.run("ESTwoKeyNav", "$search= (abc AND def) ghi ");
testUri.run("ESTwoKeyNav", "$search= abc AND (def OR ghi)");
testUri.run("ESTwoKeyNav", "$search= abc AND (def ghi)");
testUri.run("ESTwoKeyNav", "$search=(abc)");
testUri.run("ESTwoKeyNav", "$search=(abc AND def)");
testUri.run("ESTwoKeyNav", "$search=(abc AND def) OR ghi ");
testUri.run("ESTwoKeyNav", "$search=(abc AND def) ghi ");
testUri.run("ESTwoKeyNav", "$search=abc AND (def OR ghi)");
testUri.run("ESTwoKeyNav", "$search=abc AND (def ghi)");
}
@Test