org.alfresco.repo.search.impl.lucene.LuceneQueryParser.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.search.impl.lucene.LuceneQueryParser.java

Source

/*
 * #%L
 * Alfresco Legacy Lucene
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.search.impl.lucene;

import java.util.List;
import java.util.Locale;

import org.alfresco.repo.dictionary.IndexTokenisationMode;
import org.alfresco.repo.search.MLAnalysisMode;
import org.alfresco.repo.search.adaptor.lucene.AnalysisMode;
import org.alfresco.repo.search.adaptor.lucene.LuceneFunction;
import org.alfresco.repo.search.impl.lucene.analysis.PathTokenFilter;
import org.alfresco.repo.search.impl.lucene.query.CaseInsensitiveFieldQuery;
import org.alfresco.repo.search.impl.lucene.query.CaseInsensitiveFieldRangeQuery;
import org.alfresco.repo.search.impl.lucene.query.PathQuery;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.CharStream;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParserTokenManager;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.base.XPathReader;

/**
 * Extensions to the standard lucene query parser.
 * <p>
 * Covers:
 * <ul>
 * <li>special fields;
 * <li>range expansion;
 * <li>adds wild card support for phrases;
 * <li>exposes more helper methods to build lucene queries and request tokneisation bahviour.
 * </ul>
 * TODO: Locale loop should not include tokenisation expansion
 * 
 * @author andyh
 */
public class LuceneQueryParser extends AbstractLuceneQueryParser {

    private static Log s_logger = LogFactory.getLog(LuceneQueryParser.class);

    /**
     * Parses a query string, returning a {@link org.apache.lucene.search.Query}.
     * 
     * @param query
     *            the query string to be parsed.
     * @param field
     *            the default field for query terms.
     * @param analyzer
     *            used to find terms in the query text.
     * @param namespacePrefixResolver NamespacePrefixResolver
     * @param dictionaryService DictionaryService
     * @param tenantService TenantService
     * @param defaultOperator Operator
     * @param searchParameters SearchParameters
     * @param defaultSearchMLAnalysisMode MLAnalysisMode
     * @param indexReader IndexReader
     * @return - the query
     * @throws ParseException
     *             if the parsing fails
     */
    static public Query parse(String query, String field, Analyzer analyzer,
            NamespacePrefixResolver namespacePrefixResolver, DictionaryService dictionaryService,
            TenantService tenantService, Operator defaultOperator, SearchParameters searchParameters,
            MLAnalysisMode defaultSearchMLAnalysisMode, IndexReader indexReader) throws ParseException {
        if (s_logger.isDebugEnabled()) {
            s_logger.debug("Using Alfresco Lucene Query Parser for query: " + query);
        }
        LuceneQueryParser parser = new LuceneQueryParser(field, analyzer);
        parser.setDefaultOperator(defaultOperator);
        parser.setNamespacePrefixResolver(namespacePrefixResolver);
        parser.setDictionaryService(dictionaryService);
        parser.setTenantService(tenantService);
        parser.setSearchParameters(searchParameters);
        parser.setDefaultSearchMLAnalysisMode(defaultSearchMLAnalysisMode);
        parser.setIndexReader(indexReader);
        parser.setAllowLeadingWildcard(true);
        // TODO: Apply locale constraints at the top level if required for the non ML doc types.
        Query result = parser.parse(query);
        if (s_logger.isDebugEnabled()) {
            s_logger.debug("Query " + query + "                             is\n\t" + result.toString());
        }
        return result;
    }

    /**
     * Lucene default constructor
     * 
     * @param arg0 String
     * @param arg1 Analyzer
     */
    public LuceneQueryParser(String arg0, Analyzer arg1) {
        super(arg0, arg1);
    }

    /**
     * Lucene default constructor
     * 
     * @param arg0 CharStream
     */
    public LuceneQueryParser(CharStream arg0) {
        super(arg0);
    }

    /**
     * Lucene default constructor
     * 
     * @param arg0 QueryParserTokenManager
     */
    public LuceneQueryParser(QueryParserTokenManager arg0) {
        super(arg0);
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createAclIdQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createOwnerQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createReaderQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createAuthorityQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createOwnerSetQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createReaderSetQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    /**
     * @param queryText String
     * @return Query
     */
    protected Query createAuthoritySetQuery(String queryText) throws ParseException {
        return createNoMatchQuery();
    }

    protected Query createAssocTypeQNameQuery(String queryText) throws SAXPathException {
        // This was broken and using only FIELD_PRIMARYASSOCTYPEQNAME
        // The field was also not indexed correctly.
        // We do both for backward compatability ...
        BooleanQuery booleanQuery = new BooleanQuery();

        XPathReader reader = new XPathReader();
        LuceneXPathHandler handler = new LuceneXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        PathQuery query = handler.getQuery();
        query.setPathField(FIELD_PATH);
        query.setQnameField(FIELD_ASSOCTYPEQNAME);

        booleanQuery.add(query, Occur.SHOULD);
        booleanQuery.add(createPrimaryAssocTypeQNameQuery(queryText), Occur.SHOULD);

        return booleanQuery;
    }

    protected Query createPrimaryAssocTypeQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        LuceneXPathHandler handler = new LuceneXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        PathQuery query = handler.getQuery();
        query.setPathField(FIELD_PATH);
        query.setQnameField(FIELD_PRIMARYASSOCTYPEQNAME);
        return query;
    }

    protected Query createPrimaryAssocQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        LuceneXPathHandler handler = new LuceneXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse("//" + queryText);
        PathQuery query = handler.getQuery();
        query.setPathField(FIELD_PATH);
        query.setQnameField(FIELD_PRIMARYASSOCQNAME);
        return query;
    }

    protected Query createQNameQuery(String queryText) throws SAXPathException {
        XPathReader reader = new XPathReader();
        LuceneXPathHandler handler = new LuceneXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse(queryText);
        PathQuery query = handler.getQuery();
        // if it matches all docs
        if ((query.getPathStructuredFieldPositions().size() == 0)
                && (query.getQNameStructuredFieldPositions().size() == 2)) {
            if (query.getQNameStructuredFieldPositions().get(0).getTermText()
                    .equals(PathTokenFilter.NO_NS_TOKEN_TEXT)) {
                return createTermQuery(FIELD_QNAME, queryText);
            }
        }

        return createPathQuery("//" + queryText, false);
    }

    protected Query createPathQuery(String queryText, boolean withRepeats) throws SAXPathException {
        XPathReader reader = new XPathReader();
        LuceneXPathHandler handler = new LuceneXPathHandler();
        handler.setNamespacePrefixResolver(namespacePrefixResolver);
        handler.setDictionaryService(dictionaryService);
        reader.setXPathHandler(handler);
        reader.parse(queryText);
        PathQuery pathQuery = handler.getQuery();
        pathQuery.setRepeats(withRepeats);
        return pathQuery;
    }

    /**
     * @param field String
     * @param part1 String
     * @param part2 String
     * @param includeLower boolean
     * @param includeUpper boolean
     * @param analysisMode AnalysisMode
     * @param fieldName String
     * @param propertyDef PropertyDefinition
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     * @throws ParseException
     */
    protected void addTextRange(String field, String part1, String part2, boolean includeLower,
            boolean includeUpper, AnalysisMode analysisMode, String fieldName, PropertyDefinition propertyDef,
            IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode,
            Locale locale) throws ParseException {
        String textFieldName = fieldName;

        if ((analysisMode == AnalysisMode.IDENTIFIER) || (analysisMode == AnalysisMode.LIKE)) {
            {
                // text and ml text need locale
                IndexTokenisationMode tm = propertyDef.getIndexTokenisationMode();
                if ((tm != null) && (tm == IndexTokenisationMode.BOTH)) {
                    if (locale.toString().length() == 0) {
                        textFieldName = textFieldName + FIELD_NO_LOCALE_SUFFIX;
                    } else {
                        textFieldName = textFieldName + "." + locale + FIELD_SORT_SUFFIX;
                    }

                }

            }
        }
        switch (tokenisationMode) {
        case BOTH:
            switch (analysisMode) {
            case DEFAULT:
            case TOKENISE:
                addLocaleSpecificTokenisedTextRange(part1, part2, includeLower, includeUpper, analysisMode,
                        fieldName, booleanQuery, locale, textFieldName);
                break;
            case IDENTIFIER:
                addLocaleSpecificUntokenisedTextRange(field, part1, part2, includeLower, includeUpper, booleanQuery,
                        mlAnalysisMode, locale, textFieldName);
                break;
            case WILD:
            case LIKE:
            case PREFIX:
            case FUZZY:
            default:
                throw new UnsupportedOperationException();
            }
            break;
        case FALSE:
            addLocaleSpecificUntokenisedTextRange(field, part1, part2, includeLower, includeUpper, booleanQuery,
                    mlAnalysisMode, locale, textFieldName);

            break;
        case TRUE:
            addLocaleSpecificTokenisedTextRange(part1, part2, includeLower, includeUpper, analysisMode, fieldName,
                    booleanQuery, locale, textFieldName);
            break;
        default:
        }
    }

    protected void addLocaleSpecificUntokenisedTextRangeFunction(String expandedFieldName, String lower,
            String upper, boolean includeLower, boolean includeUpper, LuceneFunction luceneFunction,
            BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale,
            IndexTokenisationMode tokenisationMode) {

        if (locale.toString().length() == 0) {
            return;
        }

        String textFieldName = expandedFieldName;
        if (tokenisationMode == IndexTokenisationMode.BOTH) {
            textFieldName = textFieldName + "." + locale + FIELD_SORT_SUFFIX;
        }

        String lowerTermText = lower;
        if (locale.toString().length() > 0) {
            lowerTermText = "{" + locale + "}" + lower;
        }
        String upperTermText = upper;
        if (locale.toString().length() > 0) {
            upperTermText = "{" + locale + "}" + upper;
        }
        Query subQuery = buildRangeFunctionQuery(textFieldName, lowerTermText, upperTermText, includeLower,
                includeUpper, luceneFunction);
        booleanQuery.add(subQuery, Occur.SHOULD);

        if (booleanQuery.getClauses().length == 0) {
            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
        }
    }

    private Query buildRangeFunctionQuery(String expandedFieldName, String lowerTermText, String upperTermText,
            boolean includeLower, boolean includeUpper, LuceneFunction luceneFunction) {
        String testLowerTermText = lowerTermText;
        if (testLowerTermText.startsWith("{")) {
            int index = lowerTermText.indexOf("}");
            testLowerTermText = lowerTermText.substring(index + 1);
        }

        String testUpperTermText = upperTermText;
        if (testUpperTermText.startsWith("{")) {
            int index = upperTermText.indexOf("}");
            testUpperTermText = upperTermText.substring(index + 1);
        }

        switch (luceneFunction) {
        case LOWER:
            if (testLowerTermText.equals(testLowerTermText.toLowerCase())
                    && testUpperTermText.equals(testUpperTermText.toLowerCase())) {
                return new CaseInsensitiveFieldRangeQuery(expandedFieldName, lowerTermText, upperTermText,
                        includeLower, includeUpper);
            } else {
                // No match
                return createNoMatchQuery();
            }
        case UPPER:
            if (testLowerTermText.equals(testLowerTermText.toUpperCase())
                    && testUpperTermText.equals(testUpperTermText.toUpperCase())) {
                return new CaseInsensitiveFieldRangeQuery(expandedFieldName, lowerTermText, upperTermText,
                        includeLower, includeUpper);
            } else {
                // No match
                return createNoMatchQuery();
            }
        default:
            throw new UnsupportedOperationException("Unsupported Lucene Function " + luceneFunction);

        }
    }

    private void addLocaleSpecificTokenisedTextRange(String part1, String part2, boolean includeLower,
            boolean includeUpper, AnalysisMode analysisMode, String fieldName, BooleanQuery booleanQuery,
            Locale locale, String textFieldName) throws ParseException {
        StringBuilder builder = new StringBuilder();
        builder.append("{").append(locale.toString()).append("}").append(part1);
        String first = getToken(fieldName, builder.toString(), analysisMode);

        builder = new StringBuilder();
        builder.append("{").append(locale.toString()).append("}").append(part2);
        String last = getToken(fieldName, builder.toString(), analysisMode);

        Query query = new ConstantScoreRangeQuery(textFieldName, first, last, includeLower, includeUpper);
        booleanQuery.add(query, Occur.SHOULD);
    }

    private void addLocaleSpecificUntokenisedTextRange(String field, String part1, String part2,
            boolean includeLower, boolean includeUpper, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode,
            Locale locale, String textFieldName) {

        if (locale.toString().length() > 0) {
            String lower = "{" + locale + "}" + part1;
            String upper = "{" + locale + "}" + part2;

            Query subQuery = new ConstantScoreRangeQuery(textFieldName, lower, upper, includeLower, includeUpper);
            booleanQuery.add(subQuery, Occur.SHOULD);

            if (booleanQuery.getClauses().length == 0) {
                booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
            }
        } else {
            if ((part1.compareTo("{") > 0) || (part2.compareTo("{") < 0)) {
                Query subQuery = new ConstantScoreRangeQuery(textFieldName, part1, part2, includeLower,
                        includeUpper);
                booleanQuery.add(subQuery, Occur.SHOULD);

                if (booleanQuery.getClauses().length == 0) {
                    booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
                }
            } else {
                // Split to avoid match {en} etc
                BooleanQuery splitQuery = new BooleanQuery();

                Query lowerQuery = new ConstantScoreRangeQuery(textFieldName, part1, "{", includeLower, false);
                Query upperQuery = new ConstantScoreRangeQuery(textFieldName, "|", part2, true, includeUpper);

                splitQuery.add(lowerQuery, Occur.SHOULD);
                splitQuery.add(upperQuery, Occur.SHOULD);

                booleanQuery.add(splitQuery, Occur.SHOULD);

                if (booleanQuery.getClauses().length == 0) {
                    booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
                }
            }
        }

    }

    protected void addLocaleSpecificUntokenisedMLOrTextFunction(String expandedFieldName, String queryText,
            LuceneFunction luceneFunction, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale,
            IndexTokenisationMode tokenisationMode) {
        String textFieldName = expandedFieldName;

        if (tokenisationMode == IndexTokenisationMode.BOTH) {
            if (locale.toString().length() == 0) {
                textFieldName = textFieldName + FIELD_NO_LOCALE_SUFFIX;
            } else {
                textFieldName = textFieldName + "." + locale + FIELD_SORT_SUFFIX;
            }
        }

        String termText = queryText;
        if (locale.toString().length() > 0) {
            termText = "{" + locale + "}" + queryText;
        }
        Query subQuery = buildFunctionQuery(textFieldName, termText, luceneFunction);
        booleanQuery.add(subQuery, Occur.SHOULD);

        if (booleanQuery.getClauses().length == 0) {
            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
        }
    }

    private void addLocaleSpecificUntokenisedMLOrTextAttribute(String sourceField, String queryText,
            SubQuery subQueryBuilder, AnalysisMode analysisMode, LuceneFunction luceneFunction,
            BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale, String actualField)
            throws ParseException {

        String termText = queryText;
        if (locale.toString().length() > 0) {
            termText = "{" + locale + "}" + queryText;
        }
        Query subQuery = subQueryBuilder.getQuery(actualField, termText, analysisMode, luceneFunction);
        booleanQuery.add(subQuery, Occur.SHOULD);

        if (booleanQuery.getClauses().length == 0) {
            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
        }
    }

    private void addLocaleSpecificTokenisedMLOrTextAttribute(String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, BooleanQuery booleanQuery, Locale locale,
            String actualField) throws ParseException {
        StringBuilder builder = new StringBuilder(queryText.length() + 10);
        builder.append("{").append(locale.toString()).append("}").append(queryText);
        Query subQuery = subQueryBuilder.getQuery(actualField, builder.toString(), analysisMode, luceneFunction);
        if (subQuery != null) {
            booleanQuery.add(subQuery, Occur.SHOULD);
        }
    }

    //    private void addLocaleSpecificUntokenisedMLOrTextFunction(String expandedFieldName, String queryText, LuceneFunction luceneFunction, BooleanQuery booleanQuery,
    //            MLAnalysisMode mlAnalysisMode, Locale locale, String textFieldName)
    //    {
    //        String termText = queryText;
    //        if (locale.toString().length() > 0)
    //        {
    //            termText = "{" + locale + "}" + queryText;
    //        }
    //        Query subQuery = buildFunctionQuery(textFieldName, termText, luceneFunction);
    //        booleanQuery.add(subQuery, Occur.SHOULD);
    //
    //        if (booleanQuery.getClauses().length == 0)
    //        {
    //            booleanQuery.add(createNoMatchQuery(), Occur.SHOULD);
    //        }
    //    }

    private Query buildFunctionQuery(String expandedFieldName, String termText, LuceneFunction luceneFunction) {
        String testText = termText;
        if (termText.startsWith("{")) {
            int index = termText.indexOf("}");
            testText = termText.substring(index + 1);
        }
        switch (luceneFunction) {
        case LOWER:
            if (testText.equals(testText.toLowerCase())) {
                return new CaseInsensitiveFieldQuery(new Term(expandedFieldName, termText));
            } else {
                // No match
                return createNoMatchQuery();
            }
        case UPPER:
            if (testText.equals(testText.toUpperCase())) {
                return new CaseInsensitiveFieldQuery(new Term(expandedFieldName, termText));
            } else {
                // No match
                return createNoMatchQuery();
            }
        default:
            throw new UnsupportedOperationException("Unsupported Lucene Function " + luceneFunction);

        }
    }

    protected void addMLTextAttributeQuery(String field, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            MLAnalysisMode mlAnalysisMode, Locale locale) throws ParseException {
        String mlFieldName = expandedFieldName;

        if ((tokenisationMode == IndexTokenisationMode.BOTH)
                && ((analysisMode == AnalysisMode.IDENTIFIER) || (analysisMode == AnalysisMode.LIKE))) {
            {
                // text and ml text need locale
                IndexTokenisationMode tm = propertyDef.getIndexTokenisationMode();
                if ((tm != null) && (tm == IndexTokenisationMode.BOTH)) {
                    if (locale.toString().length() == 0) {
                        mlFieldName = mlFieldName + FIELD_NO_LOCALE_SUFFIX;
                    } else {
                        mlFieldName = mlFieldName + "." + locale + FIELD_SORT_SUFFIX;
                    }
                }

            }
        }

        boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
        try {
            switch (tokenisationMode) {
            case BOTH:
                switch (analysisMode) {
                default:
                case DEFAULT:
                case TOKENISE:
                    addLocaleSpecificTokenisedMLOrTextAttribute(queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, expandedFieldName);
                    break;
                case IDENTIFIER:
                case FUZZY:
                case PREFIX:
                case WILD:
                case LIKE:
                    setLowercaseExpandedTerms(false);
                    addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, mlAnalysisMode, locale, mlFieldName);

                    break;
                }
                break;
            case FALSE:
                setLowercaseExpandedTerms(false);
                addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                        luceneFunction, booleanQuery, mlAnalysisMode, locale, mlFieldName);
                break;
            case TRUE:
            default:
                switch (analysisMode) {
                default:
                case DEFAULT:
                case TOKENISE:
                case IDENTIFIER:
                    addLocaleSpecificTokenisedMLOrTextAttribute(queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, expandedFieldName);
                    break;
                case FUZZY:
                case PREFIX:
                case WILD:
                case LIKE:
                    addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, mlAnalysisMode, locale, mlFieldName);
                    break;
                }
            }
        } finally {
            setLowercaseExpandedTerms(lowercaseExpandedTerms);
        }
    }

    protected Query addContentAttributeQuery(String queryText, SubQuery subQueryBuilder, AnalysisMode analysisMode,
            LuceneFunction luceneFunction, String expandedFieldName, List<Locale> expandedLocales,
            MLAnalysisMode mlAnalysisMode) throws ParseException {

        if (mlAnalysisMode.includesAll()) {
            return subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
        }

        if (expandedLocales.size() > 0) {
            BooleanQuery booleanQuery = new BooleanQuery();
            Query contentQuery = subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode,
                    luceneFunction);
            if (contentQuery != null) {
                booleanQuery.add(contentQuery, Occur.MUST);
                BooleanQuery subQuery = new BooleanQuery();
                for (Locale locale : (expandedLocales)) {
                    StringBuilder builder = new StringBuilder();
                    builder.append(expandedFieldName).append(FIELD_LOCALE_SUFFIX);
                    String localeString = locale.toString();
                    if (localeString.indexOf("*") == -1) {
                        Query localeQuery = getFieldQuery(builder.toString(), localeString);
                        if (localeQuery != null) {
                            subQuery.add(localeQuery, Occur.SHOULD);
                        } else {
                            subQuery.add(createNoMatchQuery(), Occur.SHOULD);
                        }
                    } else {
                        Query localeQuery = getWildcardQuery(builder.toString(), localeString);
                        if (localeQuery != null) {
                            subQuery.add(localeQuery, Occur.SHOULD);
                        } else {
                            subQuery.add(createNoMatchQuery(), Occur.SHOULD);
                        }
                    }
                }
                booleanQuery.add(subQuery, Occur.MUST);
            }
            return booleanQuery;
        } else {
            Query query = subQueryBuilder.getQuery(expandedFieldName, queryText, analysisMode, luceneFunction);
            if (query != null) {
                return query;
            } else {
                return createNoMatchQuery();
            }
        }
    }

    /**
     * @param field String
     * @param queryText String
     * @param subQueryBuilder SubQuery
     * @param analysisMode AnalysisMode
     * @param luceneFunction LuceneFunction
     * @param expandedFieldName String
     * @param tokenisationMode IndexTokenisationMode
     * @param booleanQuery BooleanQuery
     * @param mlAnalysisMode MLAnalysisMode
     * @param locale Locale
     * @throws ParseException
     */
    protected void addTextAttributeQuery(String field, String queryText, SubQuery subQueryBuilder,
            AnalysisMode analysisMode, LuceneFunction luceneFunction, String expandedFieldName,
            IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode,
            Locale locale) throws ParseException {
        String textFieldName = expandedFieldName;

        if ((tokenisationMode == IndexTokenisationMode.BOTH)
                && ((analysisMode == AnalysisMode.IDENTIFIER) || (analysisMode == AnalysisMode.LIKE))) {
            if ((null != locale) && (0 == locale.toString().length())) {
                textFieldName += FIELD_NO_LOCALE_SUFFIX;
            } else {
                textFieldName = textFieldName + "." + locale + FIELD_SORT_SUFFIX;
            }
        }

        boolean lowercaseExpandedTerms = getLowercaseExpandedTerms();
        try {
            switch (tokenisationMode) {
            case BOTH:
                switch (analysisMode) {
                default:
                case DEFAULT:
                case TOKENISE:
                    addLocaleSpecificTokenisedMLOrTextAttribute(queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, textFieldName);
                    break;
                case IDENTIFIER:
                case FUZZY:
                case PREFIX:
                case WILD:
                case LIKE:
                    setLowercaseExpandedTerms(false);
                    addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, mlAnalysisMode, locale, textFieldName);
                    break;
                }
                break;
            case FALSE:
                setLowercaseExpandedTerms(false);
                addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                        luceneFunction, booleanQuery, mlAnalysisMode, locale, textFieldName);
                break;
            case TRUE:
            default:
                switch (analysisMode) {
                case DEFAULT:
                case TOKENISE:
                case IDENTIFIER:
                    addLocaleSpecificTokenisedMLOrTextAttribute(queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, locale, expandedFieldName);
                    break;
                case FUZZY:
                case PREFIX:
                case WILD:
                case LIKE:
                    addLocaleSpecificUntokenisedMLOrTextAttribute(field, queryText, subQueryBuilder, analysisMode,
                            luceneFunction, booleanQuery, mlAnalysisMode, locale, textFieldName);
                    break;
                }
                break;
            }
        } finally {
            setLowercaseExpandedTerms(lowercaseExpandedTerms);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser#isLucene()
     */
    @Override
    protected boolean isLucene() {
        return true;
    }

    /* (non-Javadoc)
     * @see org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser#addTextSpanQuery(java.lang.String, java.lang.String, java.lang.String, int, boolean, java.lang.String, org.alfresco.repo.dictionary.IndexTokenisationMode, org.apache.lucene.search.BooleanQuery, org.alfresco.repo.search.MLAnalysisMode, java.util.Locale)
     */
    @Override
    protected void addTextSpanQuery(String field, String first, String last, int slop, boolean inOrder,
            String expandedFieldName, IndexTokenisationMode tokenisationMode, BooleanQuery booleanQuery,
            MLAnalysisMode mlAnalysisMode, Locale locale) {
        SpanQuery firstTerm = new SpanTermQuery(new Term(field, first));
        SpanQuery lastTerm = new SpanTermQuery(new Term(field, last));
        SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
        booleanQuery.add(result, Occur.SHOULD);
    }

    /* (non-Javadoc)
     * @see org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser#addContentSpanQuery(java.lang.String, java.lang.String, java.lang.String, int, boolean, java.lang.String, java.util.List, org.alfresco.repo.search.MLAnalysisMode)
     */
    @Override
    protected org.apache.lucene.search.Query addContentSpanQuery(String field, String first, String last, int slop,
            boolean inOrder, String expandedFieldName, List<Locale> expandedLocales,
            MLAnalysisMode mlAnalysisMode) {
        SpanQuery firstTerm = new SpanTermQuery(new Term(field, first));
        SpanQuery lastTerm = new SpanTermQuery(new Term(field, last));
        SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
        return result;
    }

    /* (non-Javadoc)
     * @see org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser#addMLTextSpanQuery(java.lang.String, java.lang.String, java.lang.String, int, boolean, java.lang.String, org.alfresco.service.cmr.dictionary.PropertyDefinition, org.alfresco.repo.dictionary.IndexTokenisationMode, org.apache.lucene.search.BooleanQuery, org.alfresco.repo.search.MLAnalysisMode, java.util.Locale)
     */
    @Override
    protected void addMLTextSpanQuery(String field, String first, String last, int slop, boolean inOrder,
            String expandedFieldName, PropertyDefinition propertyDef, IndexTokenisationMode tokenisationMode,
            BooleanQuery booleanQuery, MLAnalysisMode mlAnalysisMode, Locale locale) {
        SpanQuery firstTerm = new SpanTermQuery(new Term(field, first));
        SpanQuery lastTerm = new SpanTermQuery(new Term(field, last));
        SpanNearQuery result = new SpanNearQuery(new SpanQuery[] { firstTerm, lastTerm }, slop, inOrder);
        booleanQuery.add(result, Occur.SHOULD);
    }

    /* (non-Javadoc)
     * @see org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser#addContentCrossLocaleWildcards()
     */
    @Override
    public boolean addContentCrossLocaleWildcards() {
        return true;
    }

}