You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2015/11/28 06:30:27 UTC

[19/47] olingo-odata4 git commit: [OLINGO-568] SearchParser negative tests

[OLINGO-568] SearchParser negative tests


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/8457c0f6
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/8457c0f6
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/8457c0f6

Branch: refs/heads/OLINGO-811_CountForExpand
Commit: 8457c0f60a084f92f7a98c61039c9a5540980a3c
Parents: cef72e4
Author: Christian Amend <ch...@sap.com>
Authored: Fri Nov 13 14:56:29 2015 +0100
Committer: Christian Amend <ch...@sap.com>
Committed: Fri Nov 13 14:56:29 2015 +0100

----------------------------------------------------------------------
 .../core/uri/parser/search/SearchParser.java    |  89 ++++++++------
 .../parser/search/SearchParserException.java    |  54 +++++++++
 .../core/uri/parser/search/SearchTokenizer.java | 110 ++++++++++-------
 .../parser/search/SearchTokenizerException.java |  25 +++-
 .../search/SearchParserAndTokenizerTest.java    |  10 +-
 .../uri/parser/search/SearchParserTest.java     | 118 ++++++++++++++-----
 6 files changed, 290 insertions(+), 116 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParser.java
index d5109a5..2cd03c6 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParser.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParser.java
@@ -6,9 +6,9 @@
  * 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
@@ -21,6 +21,7 @@ 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.core.uri.parser.search.SearchQueryToken.Token;
 import org.apache.olingo.server.core.uri.queryoption.SearchOptionImpl;
 
 import java.util.Iterator;
@@ -31,13 +32,11 @@ public class SearchParser {
   private Iterator<SearchQueryToken> tokens;
   private SearchQueryToken token;
 
-  public SearchOption parse(String path, String value) {
+  public SearchOption parse(String path, String value) throws SearchParserException, SearchTokenizerException {
     SearchTokenizer tokenizer = new SearchTokenizer();
     SearchExpression searchExpression;
     try {
-      tokens = tokenizer.tokenize(value).iterator();
-      nextToken();
-      searchExpression = processSearchExpression(null);
+      searchExpression = parseInternal(tokenizer.tokenize(value));
     } catch (SearchTokenizerException e) {
       return null;
     }
@@ -46,32 +45,45 @@ public class SearchParser {
     return searchOption;
   }
 
-  protected SearchExpression parseInternal(List<SearchQueryToken> tokens) {
+  protected SearchExpression parseInternal(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) {
-    if(token == null) {
+  private SearchExpression processSearchExpression(SearchExpression left) throws SearchParserException {
+    if (token == null) {
       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)) {
+    if (isToken(SearchQueryToken.Token.OPEN)) {
       processOpen();
       expression = processSearchExpression(left);
       validateToken(SearchQueryToken.Token.CLOSE);
       processClose();
-    } else if(isTerm()) {
+    } else if (isTerm()) {
       expression = processTerm();
     }
 
-    if(isToken(SearchQueryToken.Token.AND) || isTerm()) {
-        expression = processAnd(expression);
-    } else if(isToken(SearchQueryToken.Token.OR)) {
-        expression = processOr(expression);
-    } else if(isEof()) {
+    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;
@@ -88,15 +100,17 @@ public class SearchParser {
   }
 
   private boolean isToken(SearchQueryToken.Token toCheckToken) {
-    if(token == null) {
+    if (token == null) {
       return false;
     }
     return token.getToken() == toCheckToken;
   }
 
-  private void validateToken(SearchQueryToken.Token toValidateToken) {
-    if(!isToken(toValidateToken)) {
-      throw illegalState();
+  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);
     }
   }
 
@@ -108,23 +122,27 @@ public class SearchParser {
     nextToken();
   }
 
-  private SearchExpression processAnd(SearchExpression left) {
-    if(isToken(SearchQueryToken.Token.AND)) {
+  private SearchExpression processAnd(SearchExpression left) throws SearchParserException {
+    if (isToken(SearchQueryToken.Token.AND)) {
       nextToken();
     }
     SearchExpression se = left;
-    if(isTerm()) {
+    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) {
-    if(isToken(SearchQueryToken.Token.OR)) {
+  public SearchExpression processOr(SearchExpression left) throws SearchParserException {
+    if (isToken(SearchQueryToken.Token.OR)) {
       nextToken();
     }
     SearchExpression se = processSearchExpression(left);
@@ -135,30 +153,31 @@ public class SearchParser {
     return new RuntimeException();
   }
 
-  private SearchExpression processNot() {
+  private SearchExpression processNot() throws SearchParserException {
     nextToken();
-    SearchExpression searchExpression = processTerm();
-    if(searchExpression.isSearchTerm()) {
+    if (isToken(Token.WORD) || isToken(Token.PHRASE)) {
+      SearchExpression searchExpression = processTerm();
       return new SearchUnaryImpl(searchExpression.asSearchTerm());
     }
-    throw illegalState();
+    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();
+    if (tokens.hasNext()) {
+      token = tokens.next();
     } else {
       token = null;
     }
   }
 
-  private SearchExpression processTerm() {
-    if(isToken(SearchQueryToken.Token.NOT)) {
+  private SearchExpression processTerm() throws SearchParserException {
+    if (isToken(SearchQueryToken.Token.NOT)) {
       return processNot();
     }
-    if(isToken(SearchQueryToken.Token.PHRASE)) {
+    if (isToken(SearchQueryToken.Token.PHRASE)) {
       return processPhrase();
-    } else if(isToken(SearchQueryToken.Token.WORD)) {
+    } else if (isToken(SearchQueryToken.Token.WORD)) {
       return processWord();
     }
     throw illegalState();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParserException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParserException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParserException.java
new file mode 100644
index 0000000..78a12be
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchParserException.java
@@ -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);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizer.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizer.java
index a9a5895..1e3b2ef 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizer.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizer.java
@@ -6,9 +6,9 @@
  * 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
@@ -24,18 +24,18 @@ import java.util.List;
 /**
  * <code>
  * searchExpr = ( OPEN BWS searchExpr BWS CLOSE
- *  / searchTerm
- *  ) [ searchOrExpr
- *  / searchAndExpr
- *  ]
+ * / searchTerm
+ * ) [ searchOrExpr
+ * / searchAndExpr
+ * ]
  *
- *  searchOrExpr  = RWS 'OR'  RWS searchExpr
- *  searchAndExpr = RWS [ 'AND' RWS ] searchExpr
+ * 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
+ * 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 {
@@ -65,7 +65,8 @@ public class SearchTokenizer {
     }
 
     public State forbidden(char c) throws SearchTokenizerException {
-      throw new SearchTokenizerException("Forbidden character for " + this.getClass().getName() + "->" + c);
+      throw new SearchTokenizerException("Forbidden character for " + this.getClass().getName() + "->" + c,
+          SearchTokenizerException.MessageKeys.FORBIDDEN_CHARACTER, "" + c);
     }
 
     public State finish() {
@@ -97,20 +98,20 @@ public class SearchTokenizer {
     }
 
     /**
-     * unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
-     * other-delims   = "!" /                   "(" / ")" / "*" / "+" / "," / ";"
+     * 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" )
+     * 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 )
+     * qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
      *
-     * escape = "\" / "%5C"     ; reverse solidus U+005C
-     * quotation-mark  = DQUOTE / "%22"
+     * escape = "\" / "%5C" ; reverse solidus U+005C
+     * quotation-mark = DQUOTE / "%22"
      *
-     * ALPHA  = %x41-5A / %x61-7A
-     * DIGIT  = %x30-39
+     * ALPHA = %x41-5A / %x61-7A
+     * DIGIT = %x30-39
      * DQUOTE = %x22
      *
      * @param character which is checked
@@ -137,10 +138,10 @@ public class SearchTokenizer {
           || '0' <= character && character <= '9'; // case 0..9
     }
 
-    //BWS =  *( SP / HTAB / "%20" / "%09" )  ; "bad" whitespace
-    //RWS = 1*( SP / HTAB / "%20" / "%09" )  ; "required" whitespace
+    // 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" )
+      // ( SP / HTAB / "%20" / "%09" )
       // TODO mibo: add missing whitespaces
       return character == ' ' || character == '\t';
     }
@@ -158,29 +159,35 @@ public class SearchTokenizer {
 
   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.");
+      if (isFinished()) {
+        throw new SearchTokenizerException(toString() + " is already finished.",
+            SearchTokenizerException.MessageKeys.ALREADY_FINISHED);
       }
       literal.append(c);
       return this;
@@ -191,13 +198,14 @@ public class SearchTokenizer {
     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) {
+      } else if (c == CHAR_CLOSE) {
         return new CloseState();
       } else {
         return new SearchTermState().init(c);
@@ -214,9 +222,10 @@ public class SearchTokenizer {
     public SearchTermState() {
       super(Token.TERM);
     }
+
     @Override
     public State nextChar(char c) throws SearchTokenizerException {
-      if(c == CHAR_N) {
+      if (c == CHAR_N) {
         return new NotState(c);
       } else if (c == QUOTATION_MARK) {
         return new SearchPhraseState(c);
@@ -225,6 +234,7 @@ public class SearchTokenizer {
       }
       return forbidden(c);
     }
+
     @Override
     public State init(char c) throws SearchTokenizerException {
       return nextChar(c);
@@ -234,15 +244,16 @@ public class SearchTokenizer {
   private class SearchWordState extends LiteralState {
     public SearchWordState(char c) throws SearchTokenizerException {
       super(Token.WORD, c);
-      if(!isAllowedWord(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)) {
+        if (!isAllowedWord(aChar)) {
           forbidden(aChar);
         }
       }
@@ -271,7 +282,7 @@ public class SearchTokenizer {
   private class SearchPhraseState extends LiteralState {
     public SearchPhraseState(char c) throws SearchTokenizerException {
       super(Token.PHRASE, c);
-      if(c != QUOTATION_MARK) {
+      if (c != QUOTATION_MARK) {
         forbidden(c);
       }
     }
@@ -286,7 +297,7 @@ public class SearchTokenizer {
         finish();
         allowed(c);
         return new SearchExpressionState();
-      } else if(isFinished()) {
+      } else if (isFinished()) {
         return new SearchExpressionState().init(c);
       }
       return forbidden(c);
@@ -298,6 +309,7 @@ public class SearchTokenizer {
       super(Token.OPEN);
       finish();
     }
+
     @Override
     public State nextChar(char c) throws SearchTokenizerException {
       finish();
@@ -323,37 +335,40 @@ public class SearchTokenizer {
   private class NotState extends LiteralState {
     public NotState(char c) throws SearchTokenizerException {
       super(Token.NOT, c);
-      if(c != CHAR_N) {
+      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)) {
+      } 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) {
+      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)) {
+      } else if (literal.length() == 3 && isWhitespace(c)) {
         finish();
         return new BeforeSearchExpressionRwsState();
       } else {
@@ -361,18 +376,20 @@ public class SearchTokenizer {
       }
     }
   }
+
   private class OrState extends LiteralState {
     public OrState(char c) throws SearchTokenizerException {
       super(Token.OR, c);
-      if(c != CHAR_O) {
+      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)) {
+      } else if (literal.length() == 2 && isWhitespace(c)) {
         finish();
         return new BeforeSearchExpressionRwsState();
       } else {
@@ -381,12 +398,13 @@ public class SearchTokenizer {
     }
   }
 
-  // RWS 'OR'  RWS searchExpr
+  // 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)) {
@@ -401,11 +419,12 @@ public class SearchTokenizer {
     public BeforePhraseOrWordRwsState() {
       super(null);
     }
+
     @Override
     public State nextChar(char c) throws SearchTokenizerException {
       if (isWhitespace(c)) {
         return allowed(c);
-      } else if(c == '"') {
+      } else if (c == '"') {
         return new SearchPhraseState(c);
       } else {
         return new SearchWordState(c);
@@ -417,6 +436,7 @@ public class SearchTokenizer {
     public RwsState() {
       super(null);
     }
+
     @Override
     public State nextChar(char c) throws SearchTokenizerException {
       if (isWhitespace(c)) {
@@ -438,10 +458,10 @@ public class SearchTokenizer {
    * @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)
+   * (based on OData search query ABNF)
    */
   public List<SearchQueryToken> tokenize(final String searchQuery)
-        throws SearchTokenizerException {
+      throws SearchTokenizerException {
 
     char[] chars = searchQuery.trim().toCharArray();
 
@@ -455,7 +475,7 @@ public class SearchTokenizer {
       state = next;
     }
 
-    if(state.close().isFinished()) {
+    if (state.close().isFinished()) {
       states.add(state);
     }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerException.java
index 451632b..fb20efe 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerException.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerException.java
@@ -18,11 +18,30 @@
  */
 package org.apache.olingo.server.core.uri.parser.search;
 
-public class SearchTokenizerException extends Exception {
+import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
+
+public class SearchTokenizerException extends UriParserSyntaxException {
 
   private static final long serialVersionUID = -8295456415309640166L;
 
-  public SearchTokenizerException(String message) {
-    super(message);
+  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);
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
index 271b617..dd5ab70 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
@@ -33,7 +33,7 @@ import org.junit.Test;
 public class SearchParserAndTokenizerTest {
 
   @Test
-  public void basicParsing() throws SearchTokenizerException {
+  public void basicParsing() throws Exception {
     SearchExpressionValidator.init("a")
         .validate(with("a"));
     SearchExpressionValidator.init("a AND b")
@@ -172,17 +172,18 @@ public class SearchParserAndTokenizerTest {
       Assert.fail("Expected exception " + exception.getClass().getSimpleName() + " was not thrown.");
     }
 
-    private void validate(SearchExpression expectedSearchExpression) throws SearchTokenizerException {
+    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 {
+    private void validate(String expectedSearchExpression) throws SearchTokenizerException, SearchParserException {
       final SearchExpression searchExpression = getSearchExpression();
       Assert.assertEquals(expectedSearchExpression, searchExpression.toString());
     }
 
-    private SearchExpression getSearchExpression() {
+    private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
       SearchParser tokenizer = new SearchParser();
       SearchOption result = tokenizer.parse(null, searchQuery);
       Assert.assertNotNull(result);
@@ -195,5 +196,4 @@ public class SearchParserAndTokenizerTest {
     }
   }
 
-  
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8457c0f6/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserTest.java
index 89b59b4..0902e8a 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserTest.java
@@ -21,6 +21,7 @@ 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;
 
@@ -29,35 +30,35 @@ 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.Ignore;
 import org.junit.Test;
 
 public class SearchParserTest extends SearchParser {
 
   @Test
-  public void simple() {
+  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
+    // TODO: Check if quotation marks should be part of the string we deliver
     assertEquals("phrase1", se.asSearchTerm().getSearchTerm());
   }
 
   @Test
-  public void simpleAnd() {
+  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());
@@ -65,16 +66,16 @@ public class SearchParserTest extends SearchParser {
     assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
     assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
   }
-  
+
   @Test
-  public void simpleOr() {
+  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());
@@ -82,16 +83,16 @@ public class SearchParserTest extends SearchParser {
     assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
     assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
   }
-  
+
   @Test
-  public void simpleImplicitAnd() {
+  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());
@@ -99,59 +100,119 @@ public class SearchParserTest extends SearchParser {
     assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
     assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
   }
-  
+
   @Test
-  public void simpleBrackets() {
+  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() {
+  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() {
-    //word1 AND (word2 AND word3) 
+  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() {
-    //(word1 AND word2) AND word3 
+  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() {
-    //word1 AND word2 OR word3
+  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
+    // 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 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) {
+  private SearchExpression run(SearchQueryToken.Token... tokenArray) throws SearchParserException {
     List<SearchQueryToken> tokenList = prepareTokens(tokenArray);
     SearchExpression se = parseInternal(tokenList);
     assertNotNull(se);
@@ -172,6 +233,7 @@ public class SearchParserTest extends SearchParser {
         when(token.getLiteral()).thenReturn("phrase" + phraseNumber);
         phraseNumber++;
       }
+      when(token.toString()).thenReturn("" + tokenArray[i]);
       tokenList.add(token);
     }
     return tokenList;