View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.web.search;
35  
36  import fr.paris.lutece.portal.business.search.SearchParameterHome;
37  import fr.paris.lutece.portal.service.html.EncodingService;
38  import fr.paris.lutece.portal.service.i18n.I18nService;
39  import fr.paris.lutece.portal.service.message.SiteMessage;
40  import fr.paris.lutece.portal.service.message.SiteMessageException;
41  import fr.paris.lutece.portal.service.message.SiteMessageService;
42  import fr.paris.lutece.portal.service.plugin.Plugin;
43  import fr.paris.lutece.portal.service.search.ISponsoredLinksSearchService;
44  import fr.paris.lutece.portal.service.search.QueryEvent;
45  import fr.paris.lutece.portal.service.search.QueryListenersService;
46  import fr.paris.lutece.portal.service.search.SearchEngine;
47  import fr.paris.lutece.portal.service.search.SearchResult;
48  import fr.paris.lutece.portal.service.search.SearchService;
49  import fr.paris.lutece.portal.service.search.SponsoredLinksSearchService;
50  import fr.paris.lutece.portal.service.template.AppTemplateService;
51  import fr.paris.lutece.portal.service.util.AppLogService;
52  import fr.paris.lutece.portal.service.util.AppPropertiesService;
53  import fr.paris.lutece.portal.web.xpages.XPage;
54  import fr.paris.lutece.portal.web.xpages.XPageApplication;
55  import fr.paris.lutece.util.html.HtmlTemplate;
56  import fr.paris.lutece.util.html.Paginator;
57  import fr.paris.lutece.util.http.SecurityUtil;
58  import fr.paris.lutece.util.url.UrlItem;
59  
60  import org.apache.commons.lang.StringUtils;
61  
62  import java.io.UnsupportedEncodingException;
63  
64  import java.util.HashMap;
65  import java.util.List;
66  import java.util.Locale;
67  import java.util.Map;
68  
69  import javax.inject.Inject;
70  import javax.inject.Named;
71  
72  import javax.servlet.http.HttpServletRequest;
73  
74  
75  /**
76   * This class provides search results pages.
77   */
78  public class SearchApp implements XPageApplication
79  {
80      /**
81       * Serial version UID
82       */
83      private static final long serialVersionUID = -9101183157272256639L;
84  
85      ////////////////////////////////////////////////////////////////////////////
86      // Constants
87      private static final String BEAN_SEARCH_ENGINE = "searchEngine";
88      private static final String TEMPLATE_RESULTS = "skin/search/search_results.html";
89      private static final String PROPERTY_SEARCH_PAGE_URL = "search.pageSearch.baseUrl";
90      private static final String PROPERTY_RESULTS_PER_PAGE = "search.nb.docs.per.page";
91      private static final String PROPERTY_PATH_LABEL = "portal.search.search_results.pathLabel";
92      private static final String PROPERTY_PAGE_TITLE = "portal.search.search_results.pageTitle";
93      private static final String MESSAGE_INVALID_SEARCH_TERMS = "portal.search.message.invalidSearchTerms";
94      private static final String MESSAGE_ENCODING_ERROR = "portal.search.message.encodingError";
95      private static final String DEFAULT_RESULTS_PER_PAGE = "10";
96      private static final String DEFAULT_PAGE_INDEX = "1";
97      private static final String PARAMETER_PAGE_INDEX = "page_index";
98      private static final String PARAMETER_NB_ITEMS_PER_PAGE = "items_per_page";
99      private static final String PARAMETER_QUERY = "query";
100     private static final String PARAMETER_TAG_FILTER = "tag_filter";
101     private static final String PARAMETER_DEFAULT_OPERATOR = "default_operator";
102     private static final String MARK_RESULTS_LIST = "results_list";
103     private static final String MARK_QUERY = "query";
104     private static final String MARK_PAGINATOR = "paginator";
105     private static final String MARK_NB_ITEMS_PER_PAGE = "nb_items_per_page";
106     private static final String MARK_ERROR = "error";
107     private static final String MARK_SPONSOREDLINKS_SET = "sponsoredlinks_set";
108     private static final String MARK_LIST_TYPE_AND_LINK = "list_type_and_link";
109     private static final String PROPERTY_ENCODE_URI = "search.encode.uri";
110     private static final String PROPERTY_ENCODE_URI_ENCODING = "search.encode.uri.encoding";
111     private static final String DEFAULT_URI_ENCODING = "ISO-8859-1";
112     private static final String CONSTANT_ENCODING_UTF8 = "UTF-8";
113     private static final String CONSTANT_HTTP_METHOD_GET = "GET";
114     private static final boolean DEFAULT_ENCODE_URI = false;
115     @Inject
116     @Named( BEAN_SEARCH_ENGINE )
117     private SearchEngine _engine;
118 
119     /**
120      * Returns search results
121      *
122      * @param request The HTTP request.
123      * @param nMode The current mode.
124      * @param plugin The plugin
125      * @return The HTML code of the page.
126      * @throws SiteMessageException If an error occurs
127      */
128     @Override
129     public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin )
130         throws SiteMessageException
131     {
132         XPage page = new XPage(  );
133         String strQuery = request.getParameter( PARAMETER_QUERY );
134         String strTagFilter = request.getParameter( PARAMETER_TAG_FILTER );
135 
136         String strEncoding = AppPropertiesService.getProperty( PROPERTY_ENCODE_URI_ENCODING, DEFAULT_URI_ENCODING );
137 
138         if ( StringUtils.equalsIgnoreCase( CONSTANT_HTTP_METHOD_GET, request.getMethod(  ) ) &&
139                 !StringUtils.equalsIgnoreCase( strEncoding, CONSTANT_ENCODING_UTF8 ) )
140         {
141             try
142             {
143                 if ( StringUtils.isNotBlank( strQuery ) )
144                 {
145                     strQuery = new String( strQuery.getBytes( strEncoding ), CONSTANT_ENCODING_UTF8 );
146                 }
147 
148                 if ( StringUtils.isNotBlank( strTagFilter ) )
149                 {
150                     strTagFilter = new String( strTagFilter.getBytes( strEncoding ), CONSTANT_ENCODING_UTF8 );
151                 }
152             }
153             catch ( UnsupportedEncodingException e )
154             {
155                 AppLogService.error( e.getMessage(  ), e );
156             }
157         }
158 
159         if ( StringUtils.isNotEmpty( strTagFilter ) )
160         {
161             strQuery = strTagFilter;
162         }
163 
164         boolean bEncodeUri = Boolean.parseBoolean( AppPropertiesService.getProperty( PROPERTY_ENCODE_URI,
165                     Boolean.toString( DEFAULT_ENCODE_URI ) ) );
166 
167         String strSearchPageUrl = AppPropertiesService.getProperty( PROPERTY_SEARCH_PAGE_URL );
168         String strError = "";
169         Locale locale = request.getLocale(  );
170 
171         // Check XSS characters
172         if ( ( strQuery != null ) && ( SecurityUtil.containsXssCharacters( request, strQuery ) ) )
173         {
174             strError = I18nService.getLocalizedString( MESSAGE_INVALID_SEARCH_TERMS, locale );
175             strQuery = "";
176         }
177 
178         String strNbItemPerPage = request.getParameter( PARAMETER_NB_ITEMS_PER_PAGE );
179         String strDefaultNbItemPerPage = AppPropertiesService.getProperty( PROPERTY_RESULTS_PER_PAGE,
180                 DEFAULT_RESULTS_PER_PAGE );
181         strNbItemPerPage = ( strNbItemPerPage != null ) ? strNbItemPerPage : strDefaultNbItemPerPage;
182 
183         int nNbItemsPerPage = Integer.parseInt( strNbItemPerPage );
184         String strCurrentPageIndex = request.getParameter( PARAMETER_PAGE_INDEX );
185         strCurrentPageIndex = ( strCurrentPageIndex != null ) ? strCurrentPageIndex : DEFAULT_PAGE_INDEX;
186 
187         List<SearchResult> listResults = _engine.getSearchResults( strQuery, request );
188 
189         // The page should not be added to the cache
190 
191         // Notify results infos to QueryEventListeners 
192         notifyQueryListeners( strQuery, listResults.size(  ), request );
193 
194         UrlItem url = new UrlItem( strSearchPageUrl );
195         String strQueryForPaginator = strQuery;
196 
197         if ( bEncodeUri )
198         {
199             strQueryForPaginator = encodeUrl( request, strQuery );
200         }
201 
202         if ( StringUtils.isNotBlank( strTagFilter ) )
203         {
204             strQuery = "";
205         }
206 
207         url.addParameter( PARAMETER_QUERY, strQueryForPaginator );
208         url.addParameter( PARAMETER_NB_ITEMS_PER_PAGE, nNbItemsPerPage );
209 
210         StringBuilder sbUrl = new StringBuilder(  );
211         sbUrl = sbUrl.append( url.getUrl(  ) );
212 
213         if ( StringUtils.isNotBlank( request.getParameter( PARAMETER_DEFAULT_OPERATOR ) ) )
214         {
215             sbUrl = sbUrl.append( "&default_operator=" + request.getParameter( PARAMETER_DEFAULT_OPERATOR ) );
216         }
217 
218         Paginator<SearchResult> paginator = new Paginator<SearchResult>( listResults, nNbItemsPerPage,
219                 sbUrl.toString(  ), PARAMETER_PAGE_INDEX, strCurrentPageIndex );
220 
221         Map<String, Object> model = new HashMap<String, Object>(  );
222         model.put( MARK_RESULTS_LIST, paginator.getPageItems(  ) );
223         model.put( MARK_QUERY, strQuery );
224         model.put( MARK_PAGINATOR, paginator );
225         model.put( MARK_NB_ITEMS_PER_PAGE, strNbItemPerPage );
226         model.put( MARK_ERROR, strError );
227 
228         ISponsoredLinksSearchService sponsoredLinksService = new SponsoredLinksSearchService(  );
229 
230         if ( sponsoredLinksService.isAvailable(  ) )
231         {
232             model.put( MARK_SPONSOREDLINKS_SET, sponsoredLinksService.getHtmlCode( strQuery, locale ) );
233         }
234 
235         model.put( MARK_LIST_TYPE_AND_LINK, SearchService.getSearchTypesAndLinks(  ) );
236         model.putAll( SearchParameterHome.findAll(  ) );
237 
238         if ( StringUtils.isNotBlank( request.getParameter( PARAMETER_DEFAULT_OPERATOR ) ) )
239         {
240             // Override default_operator value
241             model.put( PARAMETER_DEFAULT_OPERATOR, request.getParameter( PARAMETER_DEFAULT_OPERATOR ) );
242         }
243 
244         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_RESULTS, locale, model );
245 
246         page.setPathLabel( I18nService.getLocalizedString( PROPERTY_PATH_LABEL, locale ) );
247         page.setTitle( I18nService.getLocalizedString( PROPERTY_PAGE_TITLE, locale ) );
248         page.setContent( template.getHtml(  ) );
249 
250         return page;
251     }
252 
253     /**
254      * Encode an url string
255      * @param request The HTTP request
256      * @param strSource The string to encode
257      * @return The encoded string
258      * @throws SiteMessageException If an error occurs
259      */
260     public static String encodeUrl( HttpServletRequest request, String strSource )
261         throws SiteMessageException
262     {
263         String strSourceUrl = ( strSource != null ) ? strSource : StringUtils.EMPTY;
264 
265         String strEncoded = EncodingService.encodeUrl( strSourceUrl, PROPERTY_ENCODE_URI_ENCODING, DEFAULT_URI_ENCODING );
266 
267         if ( StringUtils.isBlank( strEncoded ) && StringUtils.isNotBlank( strSourceUrl ) )
268         {
269             SiteMessageService.setMessage( request, MESSAGE_ENCODING_ERROR, SiteMessage.TYPE_ERROR );
270         }
271 
272         return strEncoded;
273     }
274 
275     /**
276      * Notify all query Listeners
277      * @param strQuery The query
278      * @param nResultsCount The results count
279      * @param request The request
280      */
281     private void notifyQueryListeners( String strQuery, int nResultsCount, HttpServletRequest request )
282     {
283         QueryEvent event = new QueryEvent(  );
284         event.setQuery( strQuery );
285         event.setResultsCount( nResultsCount );
286         event.setRequest( request );
287         QueryListenersService.getInstance(  ).notifyListeners( event );
288     }
289 }