org.sindice.siren.solr.qparser.SirenQParser.java Source code

Java tutorial

Introduction

Here is the source code for org.sindice.siren.solr.qparser.SirenQParser.java

Source

/**
 * Copyright 2014 National University of Ireland, Galway.
 *
 * This file is part of the SIREn project. Project and contact information:
 *
 *  https://github.com/rdelbru/SIREn
 *
 * Licensed 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.sindice.siren.solr.qparser;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.search.QParser;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.util.SolrPluginUtils;
import org.sindice.siren.solr.schema.Datatype;
import org.sindice.siren.solr.schema.SirenField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link SirenQParser} is in charge of parsing a SIREn query request.
 * <p>
 * Expand the query to multiple fields by constructing a disjunction of the
 * parsed query across the fields.
 * <p>
 * For each <code>nested</code> parameter in the request, its argument
 * is parsed as a subquery and added to the main query.
 * <p>
 * The default operator for use by the query parsers is {@link Operator#AND}. It
 * can be overwritten using the parameter {@link QueryParsing#OP}.
 */
public abstract class SirenQParser extends QParser {

    protected Properties qnames;

    private static final Logger logger = LoggerFactory.getLogger(SirenQParser.class);

    public SirenQParser(final String qstr, final SolrParams localParams, final SolrParams params,
            final SolrQueryRequest req) {
        super(qstr, localParams, params, req);
    }

    /**
     * Set the QNames mapping for use in the query parser.
     */
    public void setQNames(final Properties qnames) {
        this.qnames = qnames;
    }

    @Override
    public Query parse() throws ParseException {
        if (qstr == null || qstr.length() == 0)
            return null;
        final SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);
        final Map<String, Float> boosts = parseQueryFields(req.getSchema(), solrParams);

        final BooleanQuery main = this.getMainQuery(boosts, qstr);
        this.addNestedQuery(main, solrParams);

        return main;
    }

    /**
     * Build the main query that will be executed. Expand to multiple fields if
     * necessary.
     * @throws ParseException
     */
    private BooleanQuery getMainQuery(final Map<String, Float> boosts, final String qstr) throws ParseException {
        // We disable the coord because this query is an artificial construct
        final BooleanQuery query = new BooleanQuery(true);
        for (final String field : boosts.keySet()) {
            final Map<String, Analyzer> datatypeConfig = this.getDatatypeConfig(field);
            final Query q = this.parse(field, qstr, datatypeConfig);
            if (boosts.get(field) != null) {
                q.setBoost(boosts.get(field));
            }
            query.add(q, Occur.SHOULD);
        }
        return query;
    }

    /**
     * Build the nested queries and add them as a (MUST) clause of the main query.
     */
    private void addNestedQuery(final BooleanQuery main, final SolrParams solrParams) throws ParseException {
        if (solrParams.getParams("nested") != null) {
            for (final String nested : solrParams.getParams("nested")) {
                final QParser baseParser = this.subQuery(nested, null);
                main.add(baseParser.getQuery(), Occur.MUST);
            }
        }
    }

    protected abstract Query parse(final String field, final String qstr,
            final Map<String, Analyzer> datatypeConfig) throws ParseException;

    /**
     * Create a new QParser for parsing an embedded nested query.
     * <p>
     * Remove the nested parameters from the original request to avoid infinite
     * recursion.
     */
    @Override
    public QParser subQuery(final String q, final String defaultType) throws ParseException {
        final QParser nestedParser = super.subQuery(q, defaultType);
        final NamedList<Object> params = nestedParser.getParams().toNamedList();
        params.remove("nested");
        nestedParser.setParams(SolrParams.toSolrParams(params));
        return nestedParser;
    }

    /**
     * Retrieve the datatype query analyzers associated to this field
     */
    private Map<String, Analyzer> getDatatypeConfig(final String field) {
        final Map<String, Analyzer> datatypeConfig = new HashMap<String, Analyzer>();
        final SirenField fieldType = (SirenField) req.getSchema().getFieldType(field);
        final Map<String, Datatype> datatypes = fieldType.getDatatypes();

        for (final Entry<String, Datatype> e : datatypes.entrySet()) {

            if (e.getValue().getQueryAnalyzer() == null) {
                throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
                        "Configuration Error: No analyzer defined for type 'query' in " + "datatype " + e.getKey());
            }

            datatypeConfig.put(e.getKey(), e.getValue().getQueryAnalyzer());
        }

        return datatypeConfig;
    }

    protected Operator getDefaultOperator() {
        final String val = params.get(QueryParsing.OP);
        Operator defaultOp = Operator.AND; // default AND operator
        if (val != null) {
            defaultOp = "AND".equals(val) ? Operator.AND : Operator.OR;
        }
        return defaultOp;
    }

    /**
     * Uses {@link SolrPluginUtils#parseFieldBoosts(String)} with the 'qf'
     * parameter. Falls back to the 'df' parameter or
     * {@link org.apache.solr.schema.IndexSchema#getDefaultSearchFieldName()}.
     */
    public static Map<String, Float> parseQueryFields(final IndexSchema indexSchema, final SolrParams solrParams)
            throws ParseException {
        final Map<String, Float> queryFields = SolrPluginUtils
                .parseFieldBoosts(solrParams.getParams(SirenParams.QF));
        if (queryFields.isEmpty()) {
            final String df = QueryParsing.getDefaultField(indexSchema, solrParams.get(CommonParams.DF));
            if (df == null) {
                throw new ParseException("Neither " + SirenParams.QF + ", " + CommonParams.DF
                        + ", nor the default search field are present.");
            }
            queryFields.put(df, 1.0f);
        }
        checkFieldTypes(indexSchema, queryFields);
        return queryFields;
    }

    /**
     * Check if all fields are of type {@link SirenField}.
     */
    private static void checkFieldTypes(final IndexSchema indexSchema, final Map<String, Float> queryFields) {
        for (final String fieldName : queryFields.keySet()) {
            final FieldType fieldType = indexSchema.getFieldType(fieldName);
            if (!(fieldType instanceof SirenField)) {
                throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "FieldType: " + fieldName + " ("
                        + fieldType.getTypeName() + ") do not support Siren's tree query");
            }
        }
    }

}