org.sakaiproject.nakamura.api.search.solr.DomainObjectSearchQueryHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.api.search.solr.DomainObjectSearchQueryHandler.java

Source

/*
  * Licensed to the Sakai Foundation (SF) under one
  * or more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership. The SF licenses this file
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  * KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
 */
package org.sakaiproject.nakamura.api.search.solr;

import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.request.RequestParameterMap;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.search.SearchUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(componentAbstract = true)
@Service({ DomainObjectSearchQueryHandler.class, SolrSearchPropertyProvider.class,
        SolrSearchResultProcessor.class })
public abstract class DomainObjectSearchQueryHandler
        implements SolrSearchPropertyProvider, SolrSearchResultProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(DomainObjectSearchQueryHandler.class);

    private static Map<String, Object> QUERY_OPTIONS_MAP = ImmutableMap.<String, Object>of(FacetParams.FACET,
            Boolean.TRUE, FacetParams.FACET_FIELD, "tagname", FacetParams.FACET_MINCOUNT, 1);

    private static Pattern TWO_OR_MORE_STARS = Pattern.compile("\\*{2,}");

    public enum DEFAULT_REQUEST_PARAMS {
        q, tags, sortOn, sortOrder
    }

    /**
     * Preserves backwards compatibility with the old search template approach
     * until it can be discarded.
     */
    public enum TEMPLATE_PROPS {
        _q
    }

    /**
     * Most entries in the search parameters map come from HTTP request parameters.
     * These are filled in from other properties of the request.
     */
    public enum REQUEST_PARAMETERS_PROPS {
        _requestPath, _traversalDepth, _userId
    }

    @Reference
    protected SolrSearchServiceFactory searchServiceFactory;

    public DomainObjectSearchQueryHandler() {

    }

    public DomainObjectSearchQueryHandler(SolrSearchServiceFactory searchServiceFactory) {
        this.searchServiceFactory = searchServiceFactory;
    }

    /**
     * Return the search clause corresponding to the domain object. This will
     * typically be used as a Filter Query and as a fallback replacement for
     * match-all wildcards.
     */
    abstract public String getResourceTypeClause(Map<String, String> parametersMap);

    /**
     * Write the JSON object (if any) corresponding to the given result.
     */
    abstract public void writeResult(Session session, Map<String, String> parametersMap, JSONWriter jsonWriter,
            Result result) throws JSONException;

    /**
    * If the base query string would be empty, use this as the default.
    */
    public String getDefaultQueryString(Map<String, String> parametersMap) {
        // Before 4.0, the usual "*:*" pass-through would incur unexpected cost.
        // As of 4.0, filter queries are processed in parallel with the main query
        // and the performance profiles should be closer.
        return getResourceTypeClause(parametersMap);
    }

    /**
     * Return the default sort configuration for the query.
     */
    public String getDefaultSort() {
        return "score desc";
    }

    /**
     * Set up standard query option values and then give subclasses a chance
     * to refine it.
     */
    public void configureQuery(Map<String, String> parametersMap, Query query) {
        Map<String, Object> queryOptions = query.getOptions();

        // Configure the standard Filter Queries.
        Set<String> filterQueries = new HashSet<String>();
        filterQueries.add(getResourceTypeClause(parametersMap));
        queryOptions.put(CommonParams.FQ, filterQueries);

        // Configure sorting.
        final String sort;
        String sortKey = parametersMap.get(DEFAULT_REQUEST_PARAMS.sortOn.toString());
        String sortOrder = parametersMap.get(DEFAULT_REQUEST_PARAMS.sortOrder.toString());
        if ((sortKey == null) || (sortOrder == null)) {
            sort = getDefaultSort();
        } else {
            sort = sortKey + " " + sortOrder;
        }
        queryOptions.put(CommonParams.SORT, sort);

        // Configure default faceting.
        queryOptions.putAll(QUERY_OPTIONS_MAP);

        // And now let the domain-specific code have its say.
        refineQuery(parametersMap, query);
    }

    /**
     * Format the basic query string, with a hook allowing subclasses to refine it.
     */
    public String configureQString(Map<String, String> parametersMap) {
        StringBuilder qBuilder = new StringBuilder();

        String tagsParam = getSearchParam(parametersMap, DEFAULT_REQUEST_PARAMS.tags.toString());
        if (tagsParam != null) {
            if (qBuilder.length() > 0) {
                qBuilder.append(" AND ");
            }
            qBuilder.append("tag:(").append(tagsParam).append(")");
        }

        String customQueryString = buildCustomQString(parametersMap);

        if (customQueryString != null && !customQueryString.isEmpty()) {
            // append the custom query string to the solr query if one has been provided
            if (qBuilder.length() > 0) {
                qBuilder.append(" AND ");
            }
            qBuilder.append("(").append(customQueryString).append(")");
        }

        if (qBuilder.length() == 0) {
            qBuilder.append(getDefaultQueryString(parametersMap));
        }
        return qBuilder.toString();
    }

    /**
     * Utility method to eliminate blank and pure-wildcard parameter values.
     */
    public String getSearchParam(Map<String, String> parametersMap, String key) {
        String param = StringUtils.stripToNull(parametersMap.get(key));
        // solr hates long sequences of asterisks, so compress repeated *'s to single *
        if (param != null) {
            param = TWO_OR_MORE_STARS.matcher(param).replaceAll("*");
        }
        if ("*".equals(param) || "*:*".equals(param)) {
            return null;
        } else {
            return param;
        }
    }

    /**
     * Translate servlet request parameters into a properly escaped map.
     * TODO Refactor out of SolrSearchServlet for re-use.
     */
    public Map<String, String> loadParametersMap(SlingHttpServletRequest request) {
        Map<String, String> propertiesMap = new HashMap<String, String>();

        // 0. load authorizable (user) information
        String userId = request.getRemoteUser();
        propertiesMap.put(REQUEST_PARAMETERS_PROPS._userId.toString(), ClientUtils.escapeQueryChars(userId));

        // Remember the requested path, since it sometimes determines the type of query or results handling.
        propertiesMap.put(REQUEST_PARAMETERS_PROPS._requestPath.toString(), request.getRequestURI());

        // If a recursion level was specified for hierarchical results, pass it along.
        Integer traversalDepth = SearchUtil.getTraversalDepthSelector(request);
        if (traversalDepth != null) {
            propertiesMap.put(REQUEST_PARAMETERS_PROPS._traversalDepth.toString(), traversalDepth.toString());
        }

        // 2. load in properties from the request
        RequestParameterMap params = request.getRequestParameterMap();
        for (Map.Entry<String, RequestParameter[]> entry : params.entrySet()) {
            RequestParameter[] vals = entry.getValue();
            String requestValue = vals[0].getString();
            if (StringUtils.stripToNull(requestValue) != null) {
                String key = entry.getKey();
                String val = SearchUtil.escapeString(requestValue, Query.SOLR);
                propertiesMap.put(key, val);
            }
        }
        return propertiesMap;
    }

    /**
     * Add domain-specific options to the query configuration.
     */
    public void refineQuery(Map<String, String> parametersMap, Query query) {
    }

    /**
     * Add domain-specific clauses to the base query string.
     */
    public String buildCustomQString(Map<String, String> parametersMap) {
        return null;
    }

    /**
     * Preserves backwards compatibility with the old search template approach
     * until it can be discarded.
     */
    @Override
    public void loadUserProperties(SlingHttpServletRequest request, Map<String, String> propertiesMap) {
        propertiesMap.put(TEMPLATE_PROPS._q.toString(), configureQString(propertiesMap));
    }

    /**
     * Preserves backwards compatibility with the old search template approach
     * until it can be discarded.
     */
    @Override
    public SolrSearchResultSet getSearchResultSet(SlingHttpServletRequest request, Query query)
            throws SolrSearchException {
        LOGGER.debug("Input Query configuration = {}", query);
        Map<String, String> parametersMap = loadParametersMap(request);
        configureQuery(parametersMap, query);
        return searchServiceFactory.getSearchResultSet(request, query);
    }

    /**
     * Preserves backwards compatibility with the old search template approach
     * until it can be discarded.
     */
    @Override
    public void writeResult(SlingHttpServletRequest request, JSONWriter jsonWriter, Result result)
            throws JSONException {
        Session session = StorageClientUtils
                .adaptToSession(request.getResourceResolver().adaptTo(javax.jcr.Session.class));
        Map<String, String> parametersMap = loadParametersMap(request);
        writeResult(session, parametersMap, jsonWriter, result);
    }

}