fr.paris.lutece.plugins.search.solr.web.SolrSearchApp.java Source code

Java tutorial

Introduction

Here is the source code for fr.paris.lutece.plugins.search.solr.web.SolrSearchApp.java

Source

/*
 * Copyright (c) 2002-2014, Mairie de Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.search.solr.web;

import fr.paris.lutece.plugins.leaflet.business.GeolocItem;
import fr.paris.lutece.plugins.leaflet.service.IconService;
import fr.paris.lutece.plugins.search.solr.business.SolrFacetedResult;
import fr.paris.lutece.plugins.search.solr.business.SolrSearchAppConf;
import fr.paris.lutece.plugins.search.solr.business.SolrSearchEngine;
import fr.paris.lutece.plugins.search.solr.business.SolrSearchResult;
import fr.paris.lutece.plugins.search.solr.business.field.Field;
import fr.paris.lutece.plugins.search.solr.business.field.SolrFieldManager;
import fr.paris.lutece.plugins.search.solr.indexer.SolrItem;
import fr.paris.lutece.plugins.search.solr.service.ISolrSearchAppAddOn;
import fr.paris.lutece.plugins.search.solr.service.SolrSearchAppConfService;
import fr.paris.lutece.plugins.search.solr.util.SolrConstants;
import fr.paris.lutece.plugins.search.solr.util.SolrUtil;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.message.SiteMessageException;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.search.QueryEvent;
import fr.paris.lutece.portal.service.search.QueryListenersService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.web.xpages.XPage;
import fr.paris.lutece.portal.web.xpages.XPageApplication;
import fr.paris.lutece.util.html.DelegatePaginator;
import fr.paris.lutece.util.html.HtmlTemplate;
import fr.paris.lutece.util.html.IPaginator;
import fr.paris.lutece.util.html.Paginator;
import fr.paris.lutece.util.string.StringUtil;
import fr.paris.lutece.util.url.UrlItem;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeSet;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.response.SpellCheckResponse;

/**
 * This page shows some features of Solr like Highlights or Facets.
 *
 */
public class SolrSearchApp implements XPageApplication {

    private static final String FULL_URL = "fullUrl";
    private static final String SOLR_FACET_DATE_GAP = "facetDateGap";
    private static final String ALL_SEARCH_QUERY = "*:*";

    ////////////////////////////////////////////////////////////////////////////
    // Constants
    private static final String PROPERTY_SEARCH_PAGE_URL = "solr.pageSearch.baseUrl";
    private static final String PROPERTY_RESULTS_PER_PAGE = "search.nb.docs.per.page";
    private static final String PROPERTY_PATH_LABEL = "portal.search.search_results.pathLabel";
    private static final String PROPERTY_PAGE_TITLE = "portal.search.search_results.pageTitle";
    private static final String PROPERTY_ONLY_FACTES = "solr.onlyFacets";
    private static final String PROPERTY_SOLR_RESPONSE_MAX = "solr.reponse.max";
    private static final int SOLR_RESPONSE_MAX = Integer
            .parseInt(AppPropertiesService.getProperty(PROPERTY_SOLR_RESPONSE_MAX, "50"));
    private static final String MESSAGE_INVALID_SEARCH_TERMS = "portal.search.message.invalidSearchTerms";
    private static final int DEFAULT_RESULTS_PER_PAGE = 10;
    private static final String DEFAULT_PAGE_INDEX = "1";
    private static final String PARAMETER_PAGE_INDEX = "page_index";
    private static final String PARAMETER_NB_ITEMS_PER_PAGE = "items_per_page";
    private static final String PARAMETER_QUERY = "query";
    private static final String PARAMETER_CONF = "conf";
    private static final String PARAMETER_FACET_QUERY = "fq";
    private static final String PARAMETER_PREVIOUS_SEARCH = "previous_search";

    private static final String PARAMETER_FACET_LABEL = "facetlabel";
    private static final String PARAMETER_FACET_NAME = "facetname";
    private static final String PARAMETER_SORT_NAME = "sort_name";
    private static final String PARAMETER_SORT_ORDER = "sort_order";
    private static final String MARK_RESULTS_LIST = "results_list";
    private static final String MARK_QUERY = "query";
    private static final String MARK_FACET_QUERY = "facetquery";
    private static final String MARK_PAGINATOR = "paginator";
    private static final String MARK_NB_ITEMS_PER_PAGE = "nb_items_per_page";
    private static final String MARK_ERROR = "error";
    private static final String MARK_FACETS = "facets";
    private static final String MARK_SOLR_FIELDS = "solr_fields";
    private static final String MARK_FACETS_DATE = "facets_date";
    private static final String MARK_HISTORIQUE = "historique";
    private static final String MARK_SUGGESTION = "suggestion";
    private static final String MARK_SORT_NAME = "sort_name";
    private static final String MARK_SORT_ORDER = "sort_order";
    private static final String MARK_SORT_LIST = "sort_list";
    private static final String MARK_FACET_TREE = "facet_tree";
    private static final String MARK_FACETS_LIST = "facets_list";
    private static final String MARK_ENCODING = "encoding";
    private static final String MARK_CONF_QUERY = "conf_user_query";
    private static final String MARK_CONF = "conf";
    private static final String MARK_POINTS = "points";
    private static final String MARK_POINTS_GEOJSON = "geojson";
    private static final String MARK_POINTS_ID = "id";
    private static final String MARK_POINTS_FIELDCODE = "code";
    private static final String MARK_POINTS_TYPE = "type";
    private static final String PROPERTY_ENCODE_URI = "search.encode.uri";
    private static final boolean DEFAULT_ENCODE_URI = false;
    private static final boolean SOLR_SPELLCHECK = AppPropertiesService.getPropertyBoolean("solr.spellchecker",
            false);

    /**
     * Returns search results
     *
     * @param request The HTTP request.
     * @param nMode The current mode.
     * @param plugin The plugin
     * @return The HTML code of the page.
     * @throws SiteMessageException exception
     */
    @Override
    public XPage getPage(HttpServletRequest request, int nMode, Plugin plugin) throws SiteMessageException {
        XPage page = new XPage();

        String strConfCode = request.getParameter(PARAMETER_CONF);
        SolrSearchAppConf conf = SolrSearchAppConfService.loadConfiguration(strConfCode);
        if (conf == null) {
            //Use default conf if the requested one doesn't exist
            conf = SolrSearchAppConfService.loadConfiguration(null);
        }

        Map<String, Object> model = getSearchResultModel(request, conf);
        for (String beanName : conf.getAddonBeanNames()) {
            ISolrSearchAppAddOn solrSearchAppAddon = SpringContextService.getBean(beanName);
            solrSearchAppAddon.buildPageAddOn(model, request);
        }

        HtmlTemplate template = AppTemplateService.getTemplate(conf.getTemplate(), request.getLocale(), model);

        page.setPathLabel(I18nService.getLocalizedString(PROPERTY_PATH_LABEL, request.getLocale()));
        page.setTitle(I18nService.getLocalizedString(PROPERTY_PAGE_TITLE, request.getLocale()));
        page.setContent(template.getHtml());

        return page;
    }

    /**
     * @param facetQuery
     * @return
     */
    private static String getFacetNameFromIHM(String facetQuery) {
        String strFacet = null;
        String myValues[] = facetQuery.split(":", 2);
        if (myValues != null && myValues.length == 2)
            strFacet = myValues[0];
        return strFacet;
    }

    /**
     * @param facetQuery
     * @return
     */
    private static String getFacetValueFromIHM(String facetQuery) {
        String strFacet = null;
        String myValues[] = facetQuery.split(":", 2);
        if (myValues != null && myValues.length == 2)
            strFacet = myValues[1];
        return strFacet;
    }

    /**
     * Performs a search and fills the model (useful when a page needs to remind
     * search parameters/results) with the default conf
     *
     * @param request the request
     * @return the model
     * @throws SiteMessageException if an error occurs
     */
    public static Map<String, Object> getSearchResultModel(HttpServletRequest request) throws SiteMessageException {
        return getSearchResultModel(request, null);
    }

    /**
     * Performs a search and fills the model (useful when a page needs to remind
     * search parameters/results)
     *
     * @param request the request
     * @param conf the configuration
     * @return the model
     * @throws SiteMessageException if an error occurs
     */
    public static Map<String, Object> getSearchResultModel(HttpServletRequest request, SolrSearchAppConf conf)
            throws SiteMessageException {
        String strQuery = request.getParameter(PARAMETER_QUERY);
        String[] facetQuery = request.getParameterValues(PARAMETER_FACET_QUERY);
        String sort = request.getParameter(PARAMETER_SORT_NAME);
        String order = request.getParameter(PARAMETER_SORT_ORDER);
        String strCurrentPageIndex = request.getParameter(PARAMETER_PAGE_INDEX);

        String fname = StringUtils.isBlank(request.getParameter(PARAMETER_FACET_NAME)) ? null
                : request.getParameter(PARAMETER_FACET_LABEL).trim();
        String flabel = StringUtils.isBlank(request.getParameter(PARAMETER_FACET_LABEL)) ? null
                : request.getParameter(PARAMETER_FACET_LABEL).trim();
        String strConfCode = request.getParameter(PARAMETER_CONF);

        Locale locale = request.getLocale();

        if (conf == null) {
            //Use default conf if not provided
            conf = SolrSearchAppConfService.loadConfiguration(null);
        }

        StringBuilder sbFacetQueryUrl = new StringBuilder();
        SolrFieldManager sfm = new SolrFieldManager();

        List<String> lstSingleFacetQueries = new ArrayList<String>();
        Hashtable<String, Boolean> switchType = getSwitched();
        ArrayList<String> facetQueryTmp = new ArrayList<String>();
        if (facetQuery != null) {
            for (String fq : facetQuery) {
                if (sbFacetQueryUrl.indexOf(fq) == -1) {
                    String strFqNameIHM = getFacetNameFromIHM(fq);
                    String strFqValueIHM = getFacetValueFromIHM(fq);
                    if (fname == null || !switchType.containsKey(fname)
                            || (strFqNameIHM != null && strFqValueIHM != null
                                    && strFqValueIHM.equalsIgnoreCase(flabel)
                                    && strFqNameIHM.equalsIgnoreCase(fname))) {
                        sbFacetQueryUrl.append("&fq=" + fq);
                        sfm.addFacet(fq);
                        facetQueryTmp.add(fq);
                        lstSingleFacetQueries.add(fq);
                    }
                }
            }
            //             for (String fq : facetQuery)
            //            {
            //                if (sbFacetQueryUrl.indexOf(fq) == -1)
            //                {   
            //                   //   sbFacetQueryUrl.append("&fq=" + fq);
            //                       sfm.addFacet(fq);
            //                       lstSingleFacetQueries.add(fq);
            //                }
            //            }
        }
        facetQuery = new String[facetQueryTmp.size()];
        facetQuery = facetQueryTmp.toArray(facetQuery);

        if (StringUtils.isNotBlank(conf.getFilterQuery())) {
            int nNewLength = (facetQuery == null) ? 1 : (facetQuery.length + 1);
            String[] newFacetQuery = new String[nNewLength];

            for (int i = 0; i < (nNewLength - 1); i++) {
                newFacetQuery[i] = facetQuery[i];
            }

            newFacetQuery[newFacetQuery.length - 1] = conf.getFilterQuery();
            facetQuery = newFacetQuery;
        }

        boolean bEncodeUri = Boolean.parseBoolean(
                AppPropertiesService.getProperty(PROPERTY_ENCODE_URI, Boolean.toString(DEFAULT_ENCODE_URI)));

        String strSearchPageUrl = AppPropertiesService.getProperty(PROPERTY_SEARCH_PAGE_URL);
        String strError = SolrConstants.CONSTANT_EMPTY_STRING;

        int nLimit = SOLR_RESPONSE_MAX;

        // Check XSS characters
        if ((strQuery != null) && (StringUtil.containsXssCharacters(strQuery))) {
            strError = I18nService.getLocalizedString(MESSAGE_INVALID_SEARCH_TERMS, locale);
        }

        if (StringUtils.isNotBlank(strError) || StringUtils.isBlank(strQuery)) {
            strQuery = ALL_SEARCH_QUERY;

            String strOnlyFacets = AppPropertiesService.getProperty(PROPERTY_ONLY_FACTES);

            if (StringUtils.isNotBlank(strError)
                    || (((facetQuery == null) || (facetQuery.length <= 0)) && StringUtils.isNotBlank(strOnlyFacets)
                            && SolrConstants.CONSTANT_TRUE.equals(strOnlyFacets))) {
                //no request and no facet selected : we show the facets but no result
                nLimit = 0;
            }
        }

        // paginator & session related elements
        int nDefaultItemsPerPage = AppPropertiesService.getPropertyInt(PROPERTY_RESULTS_PER_PAGE,
                DEFAULT_RESULTS_PER_PAGE);
        String strCurrentItemsPerPage = request.getParameter(PARAMETER_NB_ITEMS_PER_PAGE);
        int nCurrentItemsPerPage = strCurrentItemsPerPage != null ? Integer.parseInt(strCurrentItemsPerPage) : 0;
        int nItemsPerPage = Paginator.getItemsPerPage(request, Paginator.PARAMETER_ITEMS_PER_PAGE,
                nCurrentItemsPerPage, nDefaultItemsPerPage);

        strCurrentPageIndex = (strCurrentPageIndex != null) ? strCurrentPageIndex : DEFAULT_PAGE_INDEX;

        SolrSearchEngine engine = SolrSearchEngine.getInstance();

        SolrFacetedResult facetedResult = engine.getFacetedSearchResults(strQuery, facetQuery, sort, order, nLimit,
                Integer.parseInt(strCurrentPageIndex), nItemsPerPage, SOLR_SPELLCHECK);
        List<SolrSearchResult> listResults = facetedResult.getSolrSearchResults();

        List<HashMap<String, Object>> points = null;
        if (conf.getExtraMappingQuery()) {
            List<SolrSearchResult> listResultsGeoloc = engine.getGeolocSearchResults(strQuery, facetQuery, nLimit);
            points = getGeolocModel(listResultsGeoloc);
        }

        // The page should not be added to the cache
        // Notify results infos to QueryEventListeners 
        notifyQueryListeners(strQuery, listResults.size(), request);

        UrlItem url = new UrlItem(strSearchPageUrl);
        String strQueryForPaginator = strQuery;

        if (bEncodeUri) {
            strQueryForPaginator = SolrUtil.encodeUrl(request, strQuery);
        }

        url.addParameter(PARAMETER_QUERY, strQueryForPaginator);
        url.addParameter(PARAMETER_NB_ITEMS_PER_PAGE, nItemsPerPage);

        if (strConfCode != null) {
            url.addParameter(PARAMETER_CONF, strConfCode);
        }

        for (String strFacetName : lstSingleFacetQueries) {
            url.addParameter(PARAMETER_FACET_QUERY, SolrUtil.encodeUrl(strFacetName));
        }

        // nb items per page
        IPaginator<SolrSearchResult> paginator = new DelegatePaginator<SolrSearchResult>(listResults, nItemsPerPage,
                url.getUrl(), PARAMETER_PAGE_INDEX, strCurrentPageIndex, facetedResult.getCount());

        Map<String, Object> model = new HashMap<String, Object>();
        model.put(MARK_RESULTS_LIST, paginator.getPageItems());
        // put the query only if it's not *.*
        model.put(MARK_QUERY, ALL_SEARCH_QUERY.equals(strQuery) ? SolrConstants.CONSTANT_EMPTY_STRING : strQuery);
        model.put(MARK_FACET_QUERY, sbFacetQueryUrl.toString());
        model.put(MARK_PAGINATOR, paginator);
        model.put(MARK_NB_ITEMS_PER_PAGE, nItemsPerPage);
        model.put(MARK_ERROR, strError);
        model.put(MARK_FACETS, facetedResult.getFacetFields());
        model.put(MARK_SOLR_FIELDS, SolrFieldManager.getFacetList());
        model.put(MARK_FACETS_DATE, facetedResult.getFacetDateList());
        model.put(MARK_HISTORIQUE, sfm.getCurrentFacet());
        model.put(MARK_FACETS_LIST, lstSingleFacetQueries);
        model.put(MARK_CONF_QUERY, strConfCode);
        model.put(MARK_CONF, conf);
        model.put(MARK_POINTS, points);

        if (SOLR_SPELLCHECK && (strQuery != null) && (strQuery.compareToIgnoreCase(ALL_SEARCH_QUERY) != 0)) {
            SpellCheckResponse checkResponse = engine.getSpellChecker(strQuery);

            if (checkResponse != null) {
                model.put(MARK_SUGGESTION, checkResponse.getCollatedResults());
                //model.put(MARK_SUGGESTION, facetedResult.getSolrSpellCheckResponse().getCollatedResults());
            }
        }

        model.put(MARK_SORT_NAME, sort);
        model.put(MARK_SORT_ORDER, order);
        model.put(MARK_SORT_LIST, SolrFieldManager.getSortList());
        model.put(MARK_FACET_TREE, facetedResult.getFacetIntersection());
        model.put(MARK_ENCODING, SolrUtil.getEncoding());

        String strRequestUrl = request.getRequestURL().toString();
        model.put(FULL_URL, strRequestUrl);
        model.put(SOLR_FACET_DATE_GAP, SolrSearchEngine.SOLR_FACET_DATE_GAP);

        return model;
    }

    private static Hashtable<String, Boolean> getSwitched() {
        Hashtable<String, Boolean> tabFromSwitch = new Hashtable<String, Boolean>();
        for (Field tmpField : SolrFieldManager.getFacetList().values()) {
            if (tmpField.getEnableFacet() && "SWITCH".equalsIgnoreCase(tmpField.getOperator())) {
                tabFromSwitch.put(tmpField.getName(), Boolean.FALSE);
            }
        }
        return tabFromSwitch;
    }

    private static void isSwitched(String strFacetQuery, Hashtable<String, Boolean> tabType) {
        if (strFacetQuery != null && tabType != null && tabType.containsKey(strFacetQuery)
                && tabType.get(strFacetQuery) == Boolean.FALSE) {
            tabType.remove(strFacetQuery);
            tabType.put(strFacetQuery, Boolean.TRUE);
        }
    }

    /**
     * Returns a model with points data from a geoloc search
     * @param listResultsGeoloc the result of a search
     * @return the model
     */
    private static List<HashMap<String, Object>> getGeolocModel(List<SolrSearchResult> listResultsGeoloc) {
        List<HashMap<String, Object>> points = new ArrayList<HashMap<String, Object>>(listResultsGeoloc.size());
        HashMap<String, String> iconKeysCache = new HashMap<String, String>();

        for (SolrSearchResult result : listResultsGeoloc) {
            Map<String, Object> dynamicFields = result.getDynamicFields();

            for (String key : dynamicFields.keySet()) {
                if (key.endsWith(SolrItem.DYNAMIC_GEOJSON_FIELD_SUFFIX)) {
                    HashMap<String, Object> h = new HashMap<String, Object>();
                    String strJson = (String) dynamicFields.get(key);
                    GeolocItem geolocItem = null;

                    try {
                        geolocItem = GeolocItem.fromJSON(strJson);
                    } catch (IOException e) {
                        AppLogService
                                .error("SolrSearchApp: error parsing geoloc JSON: " + strJson + ", exception " + e);
                    }

                    if (geolocItem != null) {
                        String strType = result.getId().substring(result.getId().lastIndexOf("_") + 1);
                        String strIcon;

                        if (iconKeysCache.containsKey(geolocItem.getIcon())) {
                            strIcon = iconKeysCache.get(geolocItem.getIcon());
                        } else {
                            strIcon = IconService.getIcon(strType, geolocItem.getIcon());
                            iconKeysCache.put(geolocItem.getIcon(), strIcon);
                        }

                        geolocItem.setIcon(strIcon);
                        h.put(MARK_POINTS_GEOJSON, geolocItem.toJSON());
                        h.put(MARK_POINTS_ID, result.getId().substring(result.getId().indexOf("_") + 1,
                                result.getId().lastIndexOf("_")));
                        h.put(MARK_POINTS_FIELDCODE, key.substring(0, key.lastIndexOf("_")));
                        h.put(MARK_POINTS_TYPE, strType);
                        points.add(h);
                    }
                }
            }
        }
        return points;
    }

    /**
     * Notify all query Listeners
     *
     * @param strQuery The query
     * @param nResultsCount The results count
     * @param request The request
     */
    private static void notifyQueryListeners(String strQuery, int nResultsCount, HttpServletRequest request) {
        QueryEvent event = new QueryEvent();
        event.setQuery(strQuery);
        event.setResultsCount(nResultsCount);
        event.setRequest(request);
        QueryListenersService.getInstance().notifyListeners(event);
    }

    /**
     * Return the model used during the last search
     *
     * @param request The HTTP request.
     * @return the model used during the last search
     * @deprecated model is not stored in session anymore
     */
    @Deprecated
    public static Map<String, Object> getLastSearchModel(HttpServletRequest request) {
        AppLogService
                .error("calling deprecated code : SolrSearchApp.getLastSearchModel( HttpServletRequest request )");
        return (Map<String, Object>) request.getSession().getAttribute(PARAMETER_PREVIOUS_SEARCH);
    }
}