org.xcmis.search.lucene.LuceneQueryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.xcmis.search.lucene.LuceneQueryBuilder.java

Source

/*
 * Copyright (C) 2010 eXo Platform SAS.
 * 
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
 * site: http://www.fsf.org.
 */
package org.xcmis.search.lucene;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.NumberTools;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RangeQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.regex.RegexQuery;
import org.xcmis.search.InvalidQueryException;
import org.xcmis.search.QueryObjectModelVisitor;
import org.xcmis.search.VisitException;
import org.xcmis.search.Visitors;
import org.xcmis.search.antlr.FullTextLexer;
import org.xcmis.search.antlr.FullTextParser;
import org.xcmis.search.config.IndexConfiguration;
import org.xcmis.search.lucene.content.ErrorReporterImpl;
import org.xcmis.search.lucene.index.ExtendedNumberTools;
import org.xcmis.search.lucene.index.FieldNames;
import org.xcmis.search.lucene.index.IndexException;
import org.xcmis.search.lucene.search.CaseInsensitiveRangeQuery;
import org.xcmis.search.lucene.search.CaseInsensitiveRegexCapImpl;
import org.xcmis.search.lucene.search.CaseInsensitiveTermQuery;
import org.xcmis.search.lucene.search.ChildTraversingQueryNode;
import org.xcmis.search.lucene.search.DescendantQueryNode;
import org.xcmis.search.model.Limit;
import org.xcmis.search.model.column.Column;
import org.xcmis.search.model.constraint.And;
import org.xcmis.search.model.constraint.ChildNode;
import org.xcmis.search.model.constraint.Comparison;
import org.xcmis.search.model.constraint.DescendantNode;
import org.xcmis.search.model.constraint.FullTextSearch;
import org.xcmis.search.model.constraint.Not;
import org.xcmis.search.model.constraint.Operator;
import org.xcmis.search.model.constraint.Or;
import org.xcmis.search.model.constraint.PropertyExistence;
import org.xcmis.search.model.constraint.SameNode;
import org.xcmis.search.model.operand.BindVariableName;
import org.xcmis.search.model.operand.FullTextSearchScore;
import org.xcmis.search.model.operand.Length;
import org.xcmis.search.model.operand.Literal;
import org.xcmis.search.model.operand.LowerCase;
import org.xcmis.search.model.operand.NodeDepth;
import org.xcmis.search.model.operand.NodeLocalName;
import org.xcmis.search.model.operand.NodeName;
import org.xcmis.search.model.operand.PropertyValue;
import org.xcmis.search.model.operand.UpperCase;
import org.xcmis.search.model.ordering.Ordering;
import org.xcmis.search.model.source.Join;
import org.xcmis.search.model.source.Selector;
import org.xcmis.search.model.source.join.ChildNodeJoinCondition;
import org.xcmis.search.model.source.join.DescendantNodeJoinCondition;
import org.xcmis.search.model.source.join.EquiJoinCondition;
import org.xcmis.search.model.source.join.SameNodeJoinCondition;
import org.xcmis.search.value.NameConverter;
import org.xcmis.search.value.PathSplitter;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
 * @version $Id: exo-jboss-codetemplates.xml 34360 2009-07-22 23:58:59Z
 *          aheritier $
 * 
 */
public class LuceneQueryBuilder implements QueryObjectModelVisitor {
    public static final char LIKE_ESCAPE_CHAR = '\\';

    public static final char LIKE_MATCH_ONE_CHAR = '_';

    public static final char LIKE_MATCH_ZERO_OR_MORE_CHAR = '%';

    private Stack<Object> queryBuilderStack;

    private Map<String, Object> bindVariablesValues;

    private final NameConverter nameConverter;

    private final PathSplitter pathSplitter;

    private final Pattern fullTextFieldNamePattern = Pattern.compile("^(.*:FULL|FULL):.*$");

    /**
     * Lucene index reader.
     */
    private IndexReader indexReader;

    private final IndexConfiguration indexConfiguration;

    /**
     * @param indexReader 
     * @param nameConverter 
     * @param pathSplitter
     * @param bindVariablesValues
     * @param indexConfiguration
     */
    public LuceneQueryBuilder(IndexReader indexReader, NameConverter<?> nameConverter, PathSplitter<?> pathSplitter,
            Map<String, Object> bindVariablesValues, IndexConfiguration indexConfiguration) {
        this.indexConfiguration = indexConfiguration;
        Validate.notNull(indexReader, "The indexReader argument may not be null");

        this.indexReader = indexReader;
        this.nameConverter = nameConverter;
        this.pathSplitter = pathSplitter;
        this.bindVariablesValues = bindVariablesValues;
        this.queryBuilderStack = new Stack<Object>();
    }

    /**
     * 
     * @return Return lucene query.
     */
    public Query getQuery() {
        Query result = (Query) queryBuilderStack.pop();
        this.queryBuilderStack = new Stack<Object>();
        return result;
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.And)
     */

    public void visit(And node) throws VisitException {
        // TODO check selector size;
        // final int selectors = node.getSelectorsNames().size();
        final int selectors = 1;

        if (selectors == 1) {
            // Operators will push query to stack
            Visitors.visit(node.getLeft(), this);
            Visitors.visit(node.getRight(), this);

            BooleanQuery booleanQuery = new BooleanQuery();

            booleanQuery.add((Query) queryBuilderStack.pop(), BooleanClause.Occur.MUST);
            booleanQuery.add((Query) queryBuilderStack.pop(), BooleanClause.Occur.MUST);

            queryBuilderStack.push(booleanQuery);
        } else {
            //TODO check
            throw new UnsupportedOperationException("More then one selector used");
        }

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.BindVariableName)
     */

    public void visit(BindVariableName node) throws VisitException {
        final Object variableValue = bindVariablesValues.get(nameConverter.convertName(node.getVariableName()));
        if (variableValue == null) {
            throw new VisitException("No value bound for " + node.getVariableName());
        }
        queryBuilderStack.push(variableValue);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.ChildNode)
     */

    public void visit(ChildNode node) throws VisitException {
        String parentPath = node.getParentPath();
        if (parentPath.charAt(0) == '[') {
            //uuid based absolute path
            Query parentQuery = new TermQuery(
                    new Term(FieldNames.UUID, parentPath.substring(1, parentPath.length() - 1)));
            Query childNodeQuery = new ChildTraversingQueryNode(parentQuery, false);
            queryBuilderStack.push(childNodeQuery);

        } else {

            final Object[] entries = pathSplitter.splitPath(parentPath);
            if (entries.length > 0) {
                Query childNodeQuery = null;
                for (int i = 0; i < entries.length; i++) {
                    if (i == 0) {
                        childNodeQuery = new TermQuery(new Term(FieldNames.UUID, indexConfiguration.getRootUuid()));
                    } else {
                        final String stepName = nameConverter.convertName(entries[i]);
                        final Query nameQuery = new TermQuery(new Term(FieldNames.LABEL, stepName));
                        childNodeQuery = new DescendantQueryNode(nameQuery, childNodeQuery);
                    }
                }

                // all child
                childNodeQuery = new ChildTraversingQueryNode(childNodeQuery, false);
                queryBuilderStack.push(childNodeQuery);
            } else {
                queryBuilderStack.push(new MatchAllDocsQuery());
            }
        }

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.join.ChildNodeJoinCondition)
     */

    public void visit(ChildNodeJoinCondition node) throws VisitException {
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.column.Column)
     */

    public void visit(Column node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.Comparison)
     */

    public void visit(Comparison node) throws VisitException {
        // Push static value to stack
        Visitors.visit(node.getOperand2(), this);
        // push operator to stack
        queryBuilderStack.push(node.getOperator());
        // push ignore case flag
        queryBuilderStack.push(new Boolean(false));
        // Push query from DynamicOperand to stack
        Visitors.visit(node.getOperand1(), this);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.DescendantNode)
     */

    public void visit(DescendantNode node) throws VisitException {
        String parentPath = node.getAncestorPath();
        if (parentPath.charAt(0) == '[') {
            //uuid based absolute path
            Query parentQuery = new TermQuery(
                    new Term(FieldNames.UUID, parentPath.substring(1, parentPath.length() - 1)));
            Query childNodeQuery = new ChildTraversingQueryNode(parentQuery, true);
            queryBuilderStack.push(childNodeQuery);
        } else {
            final Object[] entries = pathSplitter.splitPath(parentPath);

            Query descendantQuery = new TermQuery(new Term(FieldNames.UUID, indexConfiguration.getRootUuid()));

            for (int i = 1; i < entries.length; i++) {
                final String stepName = nameConverter.convertName(entries[i]);
                final Query nameQuery = new TermQuery(new Term(FieldNames.LABEL, stepName));
                descendantQuery = new DescendantQueryNode(nameQuery, descendantQuery);
            }
            // all childs

            descendantQuery = new ChildTraversingQueryNode(descendantQuery, true);

            queryBuilderStack.push(descendantQuery);
        }
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.join.DescendantNodeJoinCondition)
     */

    public void visit(DescendantNodeJoinCondition node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.join.EquiJoinCondition)
     */

    public void visit(EquiJoinCondition node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.FullTextSearch)
     */

    public void visit(FullTextSearch node) throws VisitException {
        // TODO selector unused, research it
        // TODO remove to string
        final CharStream input = new ANTLRStringStream(node.getFullTextSearchExpression().toString());

        final FullTextLexer lexer = new FullTextLexer(input);
        final CommonTokenStream tokens = new CommonTokenStream(lexer);
        final FullTextParser parser = new FullTextParser(tokens);

        final ErrorReporterImpl reporter = new ErrorReporterImpl();
        lexer.setErrorReporter(reporter);
        parser.setErrorReporter(reporter);

        final List<String> fields = new ArrayList<String>();
        // search by specific field
        if (node.getPropertyName() != null) {
            fields.add(FieldNames.createFullTextFieldName(node.getPropertyName()));
        } else {
            // search by all full text fields
            Set<String> names;
            try {
                names = getFieldNames();
            } catch (IndexException e) {
                throw new VisitException(e.getLocalizedMessage());
            }
            for (final String fieldName : names) {
                final Matcher matcher = fullTextFieldNamePattern.matcher(fieldName);
                if (matcher.matches()) {
                    fields.add(fieldName);
                }
            }
        }

        Query query = null;
        try {
            parser.fulltext(fields, new StandardAnalyzer());
            query = parser.getQuery();
            final InvalidQueryException ex = reporter.getException();
            if (ex != null) {
                throw new VisitException(ex.getLocalizedMessage());
            }

        } catch (final RecognitionException e) {
            throw new VisitException(e.getMessage());
        }

        queryBuilderStack.push(query);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.FullTextSearchScore)
     */

    public void visit(FullTextSearchScore node) throws VisitException {
        throw new UnsupportedOperationException("FulltextSearchScore is unsupported operation.");
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.Join)
     */

    public void visit(Join node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.Length)
     */

    public void visit(Length node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof Operator,
                "Stack should contains comparation operator ");
        Operator operator = (Operator) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof Long, "Invalid literal type, should be long. But found "
                + queryBuilderStack.peek().getClass().getCanonicalName());

        Long staticLongValue = (Long) queryBuilderStack.pop();
        String value = NumberTools.longToString(staticLongValue);
        String propertyField = FieldNames.createFieldLengthName(node.getPropertyValue().getPropertyName());

        Term lengthTerm = new Term(propertyField, value);
        switch (operator) {
        case EQUAL_TO:
            queryBuilderStack.push(new TermQuery(lengthTerm));
            break;
        case NOT_EQUAL_TO:
            final BooleanQuery booleanQuery = new BooleanQuery();

            // property exists
            booleanQuery.add(
                    new TermQuery(new Term(FieldNames.PROPERTIES_SET, node.getPropertyValue().getPropertyName())),
                    BooleanClause.Occur.SHOULD);

            booleanQuery.add(new TermQuery(lengthTerm), BooleanClause.Occur.MUST_NOT);
            queryBuilderStack.push(booleanQuery);
            break;
        case GREATER_THAN:
            queryBuilderStack.push(new RangeQuery(lengthTerm, null, false));
            break;
        case GREATER_THAN_OR_EQUAL_TO:
            queryBuilderStack.push(new RangeQuery(lengthTerm, null, true));
            break;
        case LESS_THAN:
            queryBuilderStack.push(new RangeQuery(null, lengthTerm, false));
            break;
        case LESS_THAN_OR_EQUAL_TO:
            queryBuilderStack.push(new RangeQuery(null, lengthTerm, true));
            break;
        case LIKE:
            throw new VisitException("Unsupported operation for Length operator");
        default:
            throw new VisitException("Invalid operator " + operator);
        }

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.Limit)
     */

    public void visit(Limit limit) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.Literal)
     */

    public void visit(Literal node) throws VisitException {
        queryBuilderStack.push(node.getValue());
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.LowerCase)
     */

    public void visit(LowerCase node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        final String value = (String) queryBuilderStack.peek();
        if (!caseInsensitiveSearch && !StringUtils.isAllLowerCase(value)) {
            // search nothing because static value in different case
            queryBuilderStack.push(new BooleanQuery());
        }

        queryBuilderStack.push(new Boolean(true));
        // push dynamic query to stack;
        Visitors.visit(node.getOperand(), this);
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.NodeDepth)
     */

    public void visit(NodeDepth depth) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.NodeLocalName)
     */

    public void visit(NodeLocalName node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof Operator,
                "Stack should contains comparation operator ");
        Operator operator = (Operator) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof String, "Stack should contains static value. But found "
                + queryBuilderStack.peek().getClass().getCanonicalName());
        String staticStingValue = (String) queryBuilderStack.pop();

        Term staticValueTerm = new Term(FieldNames.LABEL, staticStingValue);

        switch (operator) {
        case EQUAL_TO:

            if (caseInsensitiveSearch) {
                throw new VisitException("Unsupported operation of caseinsensetive search and NodeLocalName");
            }
            final BooleanQuery equalToQuery = new BooleanQuery();
            equalToQuery.add(new WildcardQuery(new Term(FieldNames.LABEL, "*?:" + staticStingValue)),
                    BooleanClause.Occur.SHOULD);
            equalToQuery.add(new TermQuery(staticValueTerm), BooleanClause.Occur.SHOULD);
            queryBuilderStack.push(equalToQuery);

            break;
        case NOT_EQUAL_TO:
            if (caseInsensitiveSearch) {
                throw new VisitException("Unsupported operation of caseinsensetive search and NodeLocalName");
            }
            final BooleanQuery notEqualToQuery = new BooleanQuery();
            // property exists
            notEqualToQuery.add(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD);
            final BooleanQuery q = new BooleanQuery();
            q.add(new WildcardQuery(new Term(FieldNames.LABEL, "*?:" + staticStingValue)),
                    BooleanClause.Occur.SHOULD);
            q.add(new TermQuery(new Term(FieldNames.LABEL, staticStingValue)), BooleanClause.Occur.SHOULD);

            notEqualToQuery.add(q, BooleanClause.Occur.MUST_NOT);

            queryBuilderStack.push(notEqualToQuery);
            break;
        case GREATER_THAN:
        case GREATER_THAN_OR_EQUAL_TO:
        case LESS_THAN:
        case LESS_THAN_OR_EQUAL_TO:
            throw new VisitException("Unsupported comparation :" + operator.toString() + " and NodeLocalName");

        case LIKE:

            final String likeExpression = staticStingValue;
            Query likeQuery = null;
            if (likeExpression.equals("%")) {
                // property exists
                likeQuery = new MatchAllDocsQuery();
            } else {
                final String term = "(.+:)?" + likePatternToRegex(likeExpression);
                likeQuery = new RegexQuery(new Term(FieldNames.LABEL, term));
                if (caseInsensitiveSearch) {
                    ((RegexQuery) likeQuery).setRegexImplementation(new CaseInsensitiveRegexCapImpl());
                }
            }
            queryBuilderStack.push(likeQuery);

            break;
        default:
            throw new VisitException("Invalid operator " + operator);
        }

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.NodeName)
     */

    public void visit(NodeName node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof Operator,
                "Stack should contains comparation operator ");
        Operator operator = (Operator) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof String, "Stack should contains static value. But found "
                + queryBuilderStack.peek().getClass().getCanonicalName());
        String staticStingValue = (String) queryBuilderStack.pop();

        Term staticValueTerm = new Term(FieldNames.LABEL, staticStingValue);

        switch (operator) {
        case EQUAL_TO:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(
                        new CaseInsensitiveTermQuery(new Term(FieldNames.LABEL, staticStingValue.toLowerCase())));
            } else {
                queryBuilderStack.push(new TermQuery(staticValueTerm));
            }
            break;
        case NOT_EQUAL_TO:
            final BooleanQuery booleanQuery = new BooleanQuery();
            // property exists
            booleanQuery.add(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD);
            // property not equal to
            if (caseInsensitiveSearch) {
                booleanQuery.add(new CaseInsensitiveTermQuery(staticValueTerm), BooleanClause.Occur.MUST_NOT);

            } else {
                booleanQuery.add(new TermQuery(staticValueTerm), BooleanClause.Occur.MUST_NOT);
            }
            queryBuilderStack.push(booleanQuery);
            break;
        case GREATER_THAN:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(staticValueTerm, null, false));
            } else {
                queryBuilderStack.push(new RangeQuery(staticValueTerm, null, false));
            }
            break;
        case GREATER_THAN_OR_EQUAL_TO:

            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(staticValueTerm, null, true));
            } else {
                queryBuilderStack.push(new RangeQuery(staticValueTerm, null, true));
            }
            break;
        case LESS_THAN:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(null, staticValueTerm, false));

            } else {
                queryBuilderStack.push(new RangeQuery(null, staticValueTerm, false));
            }
            break;
        case LESS_THAN_OR_EQUAL_TO:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(null, staticValueTerm, true));
            } else {
                queryBuilderStack.push(new RangeQuery(null, staticValueTerm, true));
            }
            break;
        case LIKE:

            final String likeExpression = staticStingValue;
            if (likeExpression.equals("%")) {
                // property exists
                queryBuilderStack.push(new MatchAllDocsQuery());
            } else {
                final String term = likePatternToRegex(likeExpression);
                RegexQuery query = new RegexQuery(new Term(FieldNames.LABEL, term));
                if (caseInsensitiveSearch) {
                    (query).setRegexImplementation(new CaseInsensitiveRegexCapImpl());
                }
                queryBuilderStack.push(query);
            }

            break;
        default:
            throw new VisitException("Invalid operator " + operator);
        }
    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.Not)
     */

    public void visit(Not node) throws VisitException {
        // Push query from Constraint to stack
        Visitors.visit(node.getConstraint(), this);

        final BooleanQuery resultQuery = new BooleanQuery();
        // get query builded by Constraint.
        resultQuery.add((Query) queryBuilderStack.pop(), Occur.MUST_NOT);
        // combine with previous
        if (queryBuilderStack.size() > 0) {
            resultQuery.add((Query) queryBuilderStack.pop(), Occur.MUST);
        } else {
            // TODO optimize by adding initial query
            resultQuery.add(new MatchAllDocsQuery(), Occur.MUST);
        }

        queryBuilderStack.push(resultQuery);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.Or)
     */

    public void visit(Or node) throws VisitException {
        // Push query from left constraint to stack
        Visitors.visit(node.getLeft(), this);
        // Push query from right constraint to stack
        Visitors.visit(node.getRight(), this);

        final BooleanQuery resultQuery = new BooleanQuery();
        // get query builded by left constraint.
        resultQuery.add((Query) queryBuilderStack.pop(), BooleanClause.Occur.SHOULD);
        // get query builded by right constraint.
        resultQuery.add((Query) queryBuilderStack.pop(), BooleanClause.Occur.SHOULD);

        queryBuilderStack.push(resultQuery);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.ordering.Ordering)
     */

    public void visit(Ordering node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.PropertyExistence)
     */

    public void visit(PropertyExistence node) throws VisitException {
        queryBuilderStack.push(new TermQuery(new Term(FieldNames.PROPERTIES_SET, node.getPropertyName())));

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.PropertyValue)
     */

    public void visit(PropertyValue node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        Validate.isTrue(queryBuilderStack.peek() instanceof Operator,
                "Stack should contains comparation operator ");
        Operator operator = (Operator) queryBuilderStack.pop();
        Object staticValue = queryBuilderStack.peek();
        Validate.isTrue(
                (staticValue instanceof String || staticValue instanceof Double || staticValue instanceof Long
                        || staticValue instanceof Calendar || staticValue instanceof Boolean),
                "Stack should contains static value. But found "
                        + queryBuilderStack.peek().getClass().getCanonicalName());
        staticValue = queryBuilderStack.pop();

        String staticStingValue = null;
        //convert static value to string
        //TODO check cast system
        if (staticValue instanceof String) {
            staticStingValue = (String) staticValue;
        } else if (staticValue instanceof Double) {
            staticStingValue = ExtendedNumberTools.doubleToString((Double) staticValue);
        } else if (staticValue instanceof Long) {
            staticStingValue = NumberTools.longToString((Long) staticValue);
        } else if (staticValue instanceof Calendar) {
            staticStingValue = DateTools.dateToString(((Calendar) staticValue).getTime(),
                    DateTools.Resolution.MILLISECOND);
        } else if (staticValue instanceof Boolean) {
            staticStingValue = staticValue.toString();
        }

        Term propertyValueTerm = new Term(FieldNames.createPropertyFieldName(node.getPropertyName()),
                staticStingValue);
        TermQuery propertyValueQuery = new TermQuery(propertyValueTerm);
        Term maxFildValue = new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), "\uFFFF");
        switch (operator) {
        case EQUAL_TO:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveTermQuery(propertyValueTerm));
            } else {
                queryBuilderStack.push(propertyValueQuery);
            }
            break;
        case NOT_EQUAL_TO:
            final BooleanQuery notEqualQuery = new BooleanQuery();

            // property exists
            notEqualQuery.add(new TermQuery(new Term(FieldNames.PROPERTIES_SET, node.getPropertyName())),
                    BooleanClause.Occur.SHOULD);
            // property not equal to
            if (caseInsensitiveSearch) {
                notEqualQuery.add(new CaseInsensitiveTermQuery(propertyValueTerm), BooleanClause.Occur.MUST_NOT);
            } else {
                notEqualQuery.add(propertyValueQuery, BooleanClause.Occur.MUST_NOT);
            }

            queryBuilderStack.push(notEqualQuery);
            break;
        case GREATER_THAN:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(propertyValueTerm, maxFildValue, false));
            } else {
                queryBuilderStack.push(new RangeQuery(propertyValueTerm, maxFildValue, false));
            }
            break;
        case GREATER_THAN_OR_EQUAL_TO:

            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(propertyValueTerm, maxFildValue, true));

            } else {
                queryBuilderStack.push(new RangeQuery(propertyValueTerm, maxFildValue, true));
            }
            break;
        case LESS_THAN:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(
                        new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), ""), propertyValueTerm,
                        false));

            } else {

                queryBuilderStack.push(
                        new RangeQuery(new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), ""),
                                propertyValueTerm, false));
            }
            break;
        case LESS_THAN_OR_EQUAL_TO:
            if (caseInsensitiveSearch) {
                queryBuilderStack.push(new CaseInsensitiveRangeQuery(
                        new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), ""), propertyValueTerm,
                        true));

            } else {

                queryBuilderStack.push(
                        new RangeQuery(new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), ""),
                                propertyValueTerm, true));
            }
            break;
        case LIKE:
            if (staticStingValue.equals("%")) {
                // property exists
                queryBuilderStack.push(new TermQuery(new Term(FieldNames.PROPERTIES_SET, node.getPropertyName())));
            } else {
                final String term = likePatternToRegex(staticStingValue);
                Query likeQuery = new RegexQuery(
                        new Term(FieldNames.createPropertyFieldName(node.getPropertyName()), term));
                if (caseInsensitiveSearch) {
                    ((RegexQuery) likeQuery).setRegexImplementation(new CaseInsensitiveRegexCapImpl());
                }
                queryBuilderStack.push(likeQuery);
            }

            break;
        default:
            throw new VisitException("Invalid operator " + operator);
        }

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.Query)
     */

    public void visit(org.xcmis.search.model.Query node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.constraint.SameNode)
     */

    public void visit(SameNode node) throws VisitException {
        final Object[] entries = pathSplitter.splitPath(node.getPath());
        Query descendantQuery = null;

        for (int i = 0; i < entries.length; i++) {
            if (i == 0) {
                descendantQuery = new TermQuery(new Term(FieldNames.UUID, indexConfiguration.getRootUuid()));
            } else {
                final String stepName = nameConverter.convertName(entries[i]);
                final Query nameQuery = new TermQuery(new Term(FieldNames.LABEL, stepName));
                descendantQuery = new DescendantQueryNode(nameQuery, descendantQuery);
            }
        }

        queryBuilderStack.push(descendantQuery);

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.join.SameNodeJoinCondition)
     */

    public void visit(SameNodeJoinCondition node) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.source.Selector)
     */

    public void visit(Selector selector) throws VisitException {

    }

    /**
     * @see org.xcmis.search.QueryObjectModelVisitor#visit(org.xcmis.search.model.operand.UpperCase)
     */

    public void visit(UpperCase node) throws VisitException {
        Validate.isTrue(queryBuilderStack.peek() instanceof Boolean,
                "Stack should contains caseInsensitiveSearch flag");
        boolean caseInsensitiveSearch = (Boolean) queryBuilderStack.pop();

        final String value = (String) queryBuilderStack.peek();
        if (!caseInsensitiveSearch && !StringUtils.isAllUpperCase(value)) {
            // search nothing because static value in different case
            queryBuilderStack.push(new BooleanQuery());
        }

        queryBuilderStack.push(new Boolean(true));
        // push dynamic query to stack;
        Visitors.visit(node.getOperand(), this);

    }

    /**
     * {@inheritDoc}
     */
    private Set<String> getFieldNames() throws IndexException {
        final Set<String> fildsSet = new HashSet<String>();
        @SuppressWarnings("unchecked")
        final Collection fields = indexReader.getFieldNames(IndexReader.FieldOption.ALL);
        for (final Object field : fields) {
            fildsSet.add((String) field);
        }
        return fildsSet;
    }

    /**
     * Transform Like pattern to regular expression.
     * 
     * @param pattern Like pattern
     * @return String regular expression
     */
    private String likePatternToRegex(final String pattern) {
        // - escape all non alphabetic characters
        // - escape constructs like \<alphabetic char> into \\<alphabetic char>
        // - replace non escaped _ % into . and .*
        final StringBuffer regexp = new StringBuffer();
        regexp.append("^");
        boolean escaped = false;
        for (int i = 0; i < pattern.length(); i++) {
            if (pattern.charAt(i) == LIKE_ESCAPE_CHAR) {
                if (escaped) {
                    regexp.append("\\\\");
                    escaped = false;
                } else {
                    escaped = true;
                }
            } else {
                if (Character.isLetterOrDigit(pattern.charAt(i))) {
                    if (escaped) {
                        regexp.append(pattern.charAt(i)); // append("\\\\")
                        escaped = false;
                    } else {
                        regexp.append(pattern.charAt(i));
                    }
                } else {
                    if (escaped) {
                        regexp.append('\\').append(pattern.charAt(i));
                        escaped = false;
                    } else {
                        switch (pattern.charAt(i)) {
                        case LIKE_MATCH_ONE_CHAR:
                            regexp.append('.');
                            break;
                        case LIKE_MATCH_ZERO_OR_MORE_CHAR:
                            regexp.append(".*");
                            break;
                        default:
                            regexp.append('\\').append(pattern.charAt(i));
                        }
                    }
                }
            }
        }
        regexp.append("$");
        return regexp.toString();
    }
}