org.mycore.solr.search.MCRConditionTransformer.java Source code

Java tutorial

Introduction

Here is the source code for org.mycore.solr.search.MCRConditionTransformer.java

Source

/*
 * $Id$
 * $Revision: 5697 $ $Date: May 13, 2013 $
 *
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * This program is free software; you can use it, redistribute it
 * and / or modify it under the terms of the GNU General Public License
 * (GPL) as published by the Free Software Foundation; either version 2
 * of the License or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program, in a file called gpl.txt or license.txt.
 * If not, write to the Free Software Foundation Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
 */

package org.mycore.solr.search;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrQuery.SortClause;
import org.mycore.common.MCRException;
import org.mycore.common.config.MCRConfiguration;
import org.mycore.parsers.bool.MCRAndCondition;
import org.mycore.parsers.bool.MCRCondition;
import org.mycore.parsers.bool.MCRNotCondition;
import org.mycore.parsers.bool.MCROrCondition;
import org.mycore.parsers.bool.MCRSetCondition;
import org.mycore.services.fieldquery.MCRQueryCondition;
import org.mycore.services.fieldquery.MCRSortBy;
import org.mycore.solr.MCRSolrConstants;
import org.mycore.solr.MCRSolrUtils;

/**
 * @author Thomas Scheffler (yagee)
 *
 */
public class MCRConditionTransformer {
    private static final Logger LOGGER = LogManager.getLogger(MCRConditionTransformer.class);

    /**
     * If a condition references fields from multiple indexes, this constant is
     * returned
     */
    protected static final String mixed = "--mixed--";

    private static HashSet<String> joinFields = null;

    public static String toSolrQueryString(@SuppressWarnings("rawtypes") MCRCondition condition,
            Set<String> usedFields) {
        return toSolrQueryString(condition, usedFields, false).toString();
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static StringBuilder toSolrQueryString(MCRCondition condition, Set<String> usedFields,
            boolean subCondition) {
        if (condition instanceof MCRQueryCondition) {
            MCRQueryCondition qCond = (MCRQueryCondition) condition;
            return handleQueryCondition(qCond, usedFields);
        }
        if (condition instanceof MCRSetCondition) {
            MCRSetCondition<MCRCondition> setCond = (MCRSetCondition<MCRCondition>) condition;
            return handleSetCondition(setCond, usedFields, subCondition);
        }
        if (condition instanceof MCRNotCondition) {
            MCRNotCondition notCond = (MCRNotCondition) condition;
            return handleNotCondition(notCond, usedFields);
        }
        throw new MCRException("Cannot handle MCRCondition class: " + condition.getClass().getCanonicalName());
    }

    private static StringBuilder handleQueryCondition(MCRQueryCondition qCond, Set<String> usedFields) {
        String field = qCond.getFieldName();
        String value = qCond.getValue();
        String operator = qCond.getOperator();
        usedFields.add(field);
        switch (operator) {
        case "like":
        case "contains":
            return getTermQuery(field, value.trim());
        case "=":
        case "phrase":
            return getPhraseQuery(field, value);
        case "<":
            return getLTQuery(field, value);
        case "<=":
            return getLTEQuery(field, value);
        case ">":
            return getGTQuery(field, value);
        case ">=":
            return getGTEQuery(field, value);
        }
        throw new UnsupportedOperationException("Do not know how to handle operator: " + operator);
    }

    @SuppressWarnings("rawtypes")
    private static StringBuilder handleSetCondition(MCRSetCondition<MCRCondition> setCond, Set<String> usedFields,
            boolean subCondition) {
        boolean stripPlus;
        if (setCond instanceof MCROrCondition) {
            stripPlus = true;
        } else if (setCond instanceof MCRAndCondition) {
            stripPlus = false;
        } else {
            throw new UnsupportedOperationException(
                    "Do not know how to handle " + setCond.getClass().getCanonicalName() + " set operation.");
        }
        List<MCRCondition<MCRCondition>> children = setCond.getChildren();
        if (children.isEmpty()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        boolean groupRequired = subCondition || setCond instanceof MCROrCondition;
        if (groupRequired) {
            sb.append("+(");
        }
        Iterator<MCRCondition<MCRCondition>> iterator = children.iterator();
        StringBuilder subSb = toSolrQueryString(iterator.next(), usedFields, true);
        sb.append(stripPlus ? stripPlus(subSb) : subSb);
        while (iterator.hasNext()) {
            sb.append(" ");
            subSb = toSolrQueryString(iterator.next(), usedFields, true);
            sb.append(stripPlus ? stripPlus(subSb) : subSb);
        }
        if (groupRequired) {
            sb.append(")");
        }
        return sb;
    }

    @SuppressWarnings("rawtypes")
    private static StringBuilder handleNotCondition(MCRNotCondition notCond, Set<String> usedFields) {
        MCRCondition child = notCond.getChild();
        StringBuilder sb = new StringBuilder();
        sb.append("-");
        StringBuilder solrQueryString = toSolrQueryString(child, usedFields, true);
        stripPlus(solrQueryString);
        if (solrQueryString == null || solrQueryString.length() == 0) {
            return null;
        }
        sb.append(solrQueryString);
        return sb;
    }

    private static StringBuilder getRangeQuery(String field, String lowerTerm, boolean includeLower,
            String upperTerm, boolean includeUpper) {
        StringBuilder sb = new StringBuilder();
        sb.append('+');
        sb.append(field);
        sb.append(":");
        sb.append(includeLower ? '[' : '{');
        sb.append(lowerTerm != null ? ("*".equals(lowerTerm) ? "\\*" : MCRSolrUtils.escapeSearchValue(lowerTerm))
                : "*");
        sb.append(" TO ");
        sb.append(upperTerm != null ? ("*".equals(upperTerm) ? "\\*" : MCRSolrUtils.escapeSearchValue(upperTerm))
                : "*");
        sb.append(includeUpper ? ']' : '}');
        return sb;
    }

    public static StringBuilder getLTQuery(String field, String value) {
        return getRangeQuery(field, null, true, value, false);
    }

    public static StringBuilder getLTEQuery(String field, String value) {
        return getRangeQuery(field, null, true, value, true);
    }

    public static StringBuilder getGTQuery(String field, String value) {
        return getRangeQuery(field, value, false, null, true);
    }

    public static StringBuilder getGTEQuery(String field, String value) {
        return getRangeQuery(field, value, true, null, true);
    }

    public static StringBuilder getTermQuery(String field, String value) {
        if (value.length() == 0)
            return null;
        StringBuilder sb = new StringBuilder();
        sb.append('+');
        sb.append(field);
        sb.append(":");
        String replaced = value.replaceAll("\\s+", " AND ");
        if (value.length() == replaced.length()) {
            sb.append(MCRSolrUtils.escapeSearchValue(value));
        } else {
            sb.append("(");
            sb.append(MCRSolrUtils.escapeSearchValue(replaced));
            sb.append(")");
        }
        return sb;
    }

    public static StringBuilder getPhraseQuery(String field, String value) {
        StringBuilder sb = new StringBuilder();
        sb.append('+');
        sb.append(field);
        sb.append(":");
        sb.append('"');
        sb.append(MCRSolrUtils.escapeSearchValue(value));
        sb.append('"');
        return sb;
    }

    private static StringBuilder stripPlus(StringBuilder sb) {
        if (sb == null || sb.length() == 0) {
            return sb;
        }
        if (sb.charAt(0) == '+') {
            sb.deleteCharAt(0);
        }
        return sb;
    }

    public static SolrQuery getSolrQuery(@SuppressWarnings("rawtypes") MCRCondition condition,
            List<MCRSortBy> sortBy, int maxResults) {
        String queryString = getQueryString(condition);
        SolrQuery q = applySortOptions(new SolrQuery(queryString), sortBy);
        q.setIncludeScore(true);
        q.setRows(maxResults == 0 ? Integer.MAX_VALUE : maxResults);

        String sort = q.getSortField();
        LOGGER.info("Legacy Query transformed to: " + q.getQuery() + (sort != null ? " " + sort : ""));
        return q;
    }

    public static String getQueryString(@SuppressWarnings("rawtypes") MCRCondition condition) {
        Set<String> usedFields = new HashSet<>();
        String queryString = MCRConditionTransformer.toSolrQueryString(condition, usedFields);
        return queryString;
    }

    public static SolrQuery applySortOptions(SolrQuery q, List<MCRSortBy> sortBy) {
        for (MCRSortBy option : sortBy) {
            SortClause sortClause = new SortClause(option.getFieldName(),
                    option.getSortOrder() ? ORDER.asc : ORDER.desc);
            q.addSort(sortClause);
        }
        return q;
    }

    /**
     * Builds SOLR query.
     * 
     * Automatically builds JOIN-Query if content search fields are used in query.
     * @param sortBy sort criteria
     * @param not true, if all conditions should be negated
     * @param and AND or OR connective between conditions  
     * @param table conditions per "content" or "metadata"
     * @param maxHits maximum hits
     */
    @SuppressWarnings("rawtypes")
    public static SolrQuery buildMergedSolrQuery(List<MCRSortBy> sortBy, boolean not, boolean and,
            HashMap<String, List<MCRCondition>> table, int maxHits) {
        List<MCRCondition> queryConditions = table.get("metadata");
        MCRCondition combined = buildSubCondition(queryConditions, and, not);
        SolrQuery solrRequestQuery = getSolrQuery(combined, sortBy, maxHits);

        for (Map.Entry<String, List<MCRCondition>> mapEntry : table.entrySet()) {
            if (!mapEntry.getKey().equals("metadata")) {
                MCRCondition combinedFilterQuery = buildSubCondition(mapEntry.getValue(), and, not);
                SolrQuery filterQuery = getSolrQuery(combinedFilterQuery, sortBy, maxHits);
                solrRequestQuery.addFilterQuery(MCRSolrConstants.JOIN_PATTERN + filterQuery.getQuery());
            }
        }
        return solrRequestQuery;
    }

    /** Builds a new condition for all fields from one single index */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected static MCRCondition buildSubCondition(List<MCRCondition> conditions, boolean and, boolean not) {
        MCRCondition subCond;
        if (conditions.size() == 1) {
            subCond = conditions.get(0);
        } else if (and) {
            subCond = new MCRAndCondition().addAll(conditions);
        } else {
            subCond = new MCROrCondition().addAll(conditions);
        }
        if (not) {
            subCond = new MCRNotCondition(subCond);
        }
        return subCond;
    }

    /**
     * Build a table from index ID to a List of conditions referencing this
     * index
     */
    @SuppressWarnings("rawtypes")
    public static HashMap<String, List<MCRCondition>> groupConditionsByIndex(MCRSetCondition cond) {
        HashMap<String, List<MCRCondition>> table = new HashMap<String, List<MCRCondition>>();
        @SuppressWarnings("unchecked")
        List<MCRCondition> children = cond.getChildren();

        for (MCRCondition child : children) {
            String index = getIndex(child);
            List<MCRCondition> conditions = table.get(index);
            if (conditions == null) {
                conditions = new ArrayList<MCRCondition>();
                table.put(index, conditions);
            }
            conditions.add(child);
        }
        return table;
    }

    /**
     * Returns the ID of the index of all fields referenced in this condition.
     * If the fields come from multiple indexes, the constant mixed is returned.
     */
    @SuppressWarnings("rawtypes")
    private static String getIndex(MCRCondition cond) {
        if (cond instanceof MCRQueryCondition) {
            MCRQueryCondition queryCondition = ((MCRQueryCondition) cond);
            String fieldName = queryCondition.getFieldName();
            return getIndex(fieldName);
        } else if (cond instanceof MCRNotCondition) {
            return getIndex(((MCRNotCondition) cond).getChild());
        }

        @SuppressWarnings("unchecked")
        List<MCRCondition> children = ((MCRSetCondition) cond).getChildren();

        // mixed indexes here!
        return children.stream().map(MCRConditionTransformer::getIndex).reduce((l, r) -> l.equals(r) ? l : mixed)
                .get();
    }

    public static String getIndex(String fieldName) {
        return getJoinFields().contains(fieldName) ? "content" : "metadata";
    }

    private static HashSet<String> getJoinFields() {
        if (joinFields == null) {
            joinFields = new HashSet<>(MCRConfiguration.instance().getStrings("MCR.Module-solr.JoinQueryFields"));
        }
        return joinFields;
    }

}