mirror of
https://github.com/apache/olingo-odata4.git
synced 2025-03-04 23:59:09 +00:00
[OLINGO-568] Merge branch 'OLINGO-568_SearchParser_Draft'
This commit is contained in:
commit
1a59a5804b
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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 + '}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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 + "'";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + '}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user