SearchApp.java
- /*
- * Copyright (c) 2002-2022, City of 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.portal.web.search;
- import fr.paris.lutece.portal.business.search.SearchParameterHome;
- import fr.paris.lutece.portal.service.html.EncodingService;
- import fr.paris.lutece.portal.service.i18n.I18nService;
- import fr.paris.lutece.portal.service.message.SiteMessage;
- import fr.paris.lutece.portal.service.message.SiteMessageException;
- import fr.paris.lutece.portal.service.message.SiteMessageService;
- import fr.paris.lutece.portal.service.plugin.Plugin;
- import fr.paris.lutece.portal.service.search.ISponsoredLinksSearchService;
- import fr.paris.lutece.portal.service.search.QueryEvent;
- import fr.paris.lutece.portal.service.search.QueryListenersService;
- import fr.paris.lutece.portal.service.search.SearchEngine;
- import fr.paris.lutece.portal.service.search.SearchResult;
- import fr.paris.lutece.portal.service.search.SearchService;
- import fr.paris.lutece.portal.service.search.SponsoredLinksSearchService;
- 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.HtmlTemplate;
- import fr.paris.lutece.util.html.Paginator;
- import fr.paris.lutece.util.http.SecurityUtil;
- import fr.paris.lutece.util.url.UrlItem;
- import org.apache.commons.lang3.StringUtils;
- import java.io.UnsupportedEncodingException;
- import java.nio.charset.StandardCharsets;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Optional;
- import javax.inject.Inject;
- import javax.inject.Named;
- import javax.servlet.http.HttpServletRequest;
- /**
- * This class provides search results pages.
- */
- public class SearchApp implements XPageApplication
- {
- /**
- * Serial version UID
- */
- private static final long serialVersionUID = -9101183157272256639L;
- // //////////////////////////////////////////////////////////////////////////
- // Constants
- private static final String BEAN_SEARCH_ENGINE = "searchEngine";
- private static final String TEMPLATE_RESULTS = "skin/search/search_results.html";
- private static final String PROPERTY_SEARCH_PAGE_URL = "search.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 MESSAGE_INVALID_SEARCH_TERMS = "portal.search.message.invalidSearchTerms";
- private static final String MESSAGE_ENCODING_ERROR = "portal.search.message.encodingError";
- private static final String 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_TAG_FILTER = "tag_filter";
- private static final String PARAMETER_DEFAULT_OPERATOR = "default_operator";
- private static final String MARK_RESULTS_LIST = "results_list";
- private static final String MARK_QUERY = "query";
- 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_SPONSOREDLINKS_SET = "sponsoredlinks_set";
- private static final String MARK_LIST_TYPE_AND_LINK = "list_type_and_link";
- private static final String PROPERTY_ENCODE_URI = "search.encode.uri";
- private static final String PROPERTY_ENCODE_URI_ENCODING = "search.encode.uri.encoding";
- private static final String CONSTANT_HTTP_METHOD_GET = "GET";
- private static final boolean DEFAULT_ENCODE_URI = false;
- @Inject
- @Named( BEAN_SEARCH_ENGINE )
- private SearchEngine _engine;
- /**
- * 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
- * If an error occurs
- */
- @Override
- public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin ) throws SiteMessageException
- {
- XPage page = new XPage( );
- String strQuery = request.getParameter( PARAMETER_QUERY );
- String strTagFilter = request.getParameter( PARAMETER_TAG_FILTER );
- String strEncoding = AppPropertiesService.getProperty( PROPERTY_ENCODE_URI_ENCODING, StandardCharsets.ISO_8859_1.name( ) );
- if ( StringUtils.equalsIgnoreCase( CONSTANT_HTTP_METHOD_GET, request.getMethod( ) )
- && !StringUtils.equalsIgnoreCase( strEncoding, StandardCharsets.UTF_8.name( ) ) )
- {
- try
- {
- if ( StringUtils.isNotBlank( strQuery ) )
- {
- strQuery = new String( strQuery.getBytes( strEncoding ), StandardCharsets.UTF_8 );
- }
- if ( StringUtils.isNotBlank( strTagFilter ) )
- {
- strTagFilter = new String( strTagFilter.getBytes( strEncoding ), StandardCharsets.UTF_8 );
- }
- }
- catch( UnsupportedEncodingException e )
- {
- AppLogService.error( e.getMessage( ), e );
- }
- }
- if ( StringUtils.isNotEmpty( strTagFilter ) )
- {
- strQuery = strTagFilter;
- }
- boolean bEncodeUri = Boolean.parseBoolean( AppPropertiesService.getProperty( PROPERTY_ENCODE_URI, Boolean.toString( DEFAULT_ENCODE_URI ) ) );
- String strSearchPageUrl = AppPropertiesService.getProperty( PROPERTY_SEARCH_PAGE_URL );
- String strError = "";
- Locale locale = request.getLocale( );
- // Check XSS characters
- if ( ( strQuery != null ) && ( SecurityUtil.containsXssCharacters( request, strQuery ) ) )
- {
- strError = I18nService.getLocalizedString( MESSAGE_INVALID_SEARCH_TERMS, locale );
- strQuery = "";
- }
- String strDefaultNbItemPerPage = AppPropertiesService.getProperty( PROPERTY_RESULTS_PER_PAGE, DEFAULT_RESULTS_PER_PAGE );
- String strNbItemPerPage = Optional.ofNullable( request.getParameter( PARAMETER_NB_ITEMS_PER_PAGE ) ).orElse( strDefaultNbItemPerPage );
- int nNbItemsPerPage = Integer.parseInt( strNbItemPerPage );
- String strCurrentPageIndex = Optional.ofNullable( request.getParameter( PARAMETER_PAGE_INDEX ) ).orElse( DEFAULT_PAGE_INDEX );
- List<SearchResult> listResults = _engine.getSearchResults( strQuery, request );
- // 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 = encodeUrl( request, strQuery );
- }
- if ( StringUtils.isNotBlank( strTagFilter ) )
- {
- strQuery = "";
- }
- url.addParameter( PARAMETER_QUERY, strQueryForPaginator );
- url.addParameter( PARAMETER_NB_ITEMS_PER_PAGE, nNbItemsPerPage );
- StringBuilder sbUrl = new StringBuilder( );
- sbUrl = sbUrl.append( url.getUrl( ) );
- Map<String, Object> model = new HashMap<>( );
- if ( StringUtils.isNotBlank( request.getParameter( PARAMETER_DEFAULT_OPERATOR ) ) )
- {
- sbUrl = sbUrl.append( "&default_operator=" + request.getParameter( PARAMETER_DEFAULT_OPERATOR ) );
- // Override default_operator value
- model.put( PARAMETER_DEFAULT_OPERATOR, request.getParameter( PARAMETER_DEFAULT_OPERATOR ) );
- }
- Paginator<SearchResult> paginator = new Paginator<>( listResults, nNbItemsPerPage, sbUrl.toString( ), PARAMETER_PAGE_INDEX, strCurrentPageIndex );
- model.put( MARK_RESULTS_LIST, paginator.getPageItems( ) );
- model.put( MARK_QUERY, strQuery );
- model.put( MARK_PAGINATOR, paginator );
- model.put( MARK_NB_ITEMS_PER_PAGE, strNbItemPerPage );
- model.put( MARK_ERROR, strError );
- ISponsoredLinksSearchService sponsoredLinksService = new SponsoredLinksSearchService( );
- if ( sponsoredLinksService.isAvailable( ) )
- {
- model.put( MARK_SPONSOREDLINKS_SET, sponsoredLinksService.getHtmlCode( strQuery, locale ) );
- }
- model.put( MARK_LIST_TYPE_AND_LINK, SearchService.getSearchTypesAndLinks( ) );
- model.putAll( SearchParameterHome.findAll( ) );
- HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_RESULTS, locale, model );
- page.setPathLabel( I18nService.getLocalizedString( PROPERTY_PATH_LABEL, locale ) );
- page.setTitle( I18nService.getLocalizedString( PROPERTY_PAGE_TITLE, locale ) );
- page.setContent( template.getHtml( ) );
- return page;
- }
- /**
- * Encode an url string
- *
- * @param request
- * The HTTP request
- * @param strSource
- * The string to encode
- * @return The encoded string
- * @throws SiteMessageException
- * If an error occurs
- */
- public static String encodeUrl( HttpServletRequest request, String strSource ) throws SiteMessageException
- {
- String strSourceUrl = ( strSource != null ) ? strSource : StringUtils.EMPTY;
- String strEncoded = EncodingService.encodeUrl( strSourceUrl, PROPERTY_ENCODE_URI_ENCODING, StandardCharsets.ISO_8859_1.name( ) );
- if ( StringUtils.isBlank( strEncoded ) && StringUtils.isNotBlank( strSourceUrl ) )
- {
- SiteMessageService.setMessage( request, MESSAGE_ENCODING_ERROR, SiteMessage.TYPE_ERROR );
- }
- return strEncoded;
- }
- /**
- * Notify all query Listeners
- *
- * @param strQuery
- * The query
- * @param nResultsCount
- * The results count
- * @param request
- * The request
- */
- private 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 );
- }
- }