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.service.search;
35  
36  import fr.paris.lutece.portal.business.page.Page;
37  import fr.paris.lutece.portal.service.security.LuteceUser;
38  import fr.paris.lutece.portal.service.security.SecurityService;
39  import fr.paris.lutece.portal.service.util.AppLogService;
40  import fr.paris.lutece.util.date.DateUtil;
41  
42  import org.apache.commons.lang.StringUtils;
43  
44  import org.apache.lucene.document.DateTools;
45  import org.apache.lucene.document.DateTools.Resolution;
46  import org.apache.lucene.document.Document;
47  import org.apache.lucene.index.DirectoryReader;
48  import org.apache.lucene.index.IndexReader;
49  import org.apache.lucene.index.Term;
50  import org.apache.lucene.queries.ChainedFilter;
51  import org.apache.lucene.queryparser.classic.QueryParser;
52  import org.apache.lucene.search.BooleanClause;
53  import org.apache.lucene.search.BooleanQuery;
54  import org.apache.lucene.search.CachingWrapperFilter;
55  import org.apache.lucene.search.Filter;
56  import org.apache.lucene.search.IndexSearcher;
57  import org.apache.lucene.search.Query;
58  import org.apache.lucene.search.QueryWrapperFilter;
59  import org.apache.lucene.search.ScoreDoc;
60  import org.apache.lucene.search.TermQuery;
61  import org.apache.lucene.search.TermRangeQuery;
62  import org.apache.lucene.search.TopDocs;
63  import org.apache.lucene.util.BytesRef;
64  
65  import java.text.ParseException;
66  
67  import java.util.ArrayList;
68  import java.util.Date;
69  import java.util.List;
70  
71  import javax.servlet.http.HttpServletRequest;
72  
73  
74  /**
75   * LuceneSearchEngine
76   */
77  public class LuceneSearchEngine implements SearchEngine
78  {
79      public static final int MAX_RESPONSES = 1000000;
80      private static final String PARAMETER_TYPE_FILTER = "type_filter";
81      private static final String PARAMETER_DATE_AFTER = "date_after";
82      private static final String PARAMETER_DATE_BEFORE = "date_before";
83      private static final String PARAMETER_TAG_FILTER = "tag_filter";
84      private static final String PARAMETER_DEFAULT_OPERATOR = "default_operator";
85      private static final String PARAMETER_OPERATOR_AND = "AND";
86  
87      /**
88       * Return search results
89       *
90       * @param strQuery The search query
91       * @param request The HTTP request
92       * @return Results as a collection of SearchResult
93       */
94      public List<SearchResult> getSearchResults( String strQuery, HttpServletRequest request )
95      {
96          ArrayList<SearchItem> listResults = new ArrayList<SearchItem>(  );
97          ArrayList<Filter> listFilter = new ArrayList<Filter>(  );
98          IndexSearcher searcher = null;
99          boolean bFilterResult = false;
100         LuteceUser user = null;
101         String[] typeFilter = request.getParameterValues( PARAMETER_TYPE_FILTER );
102         String strDateAfter = request.getParameter( PARAMETER_DATE_AFTER );
103         String strDateBefore = request.getParameter( PARAMETER_DATE_BEFORE );
104         boolean bDateAfter = false;
105         boolean bDateBefore = false;
106         Filter allFilter = null;
107         String strTagFilter = request.getParameter( PARAMETER_TAG_FILTER );
108 
109         if ( SecurityService.isAuthenticationEnable(  ) )
110         {
111             user = SecurityService.getInstance(  ).getRegisteredUser( request );
112 
113             Filter[] filtersRole = null;
114 
115             if ( user != null )
116             {
117                 String[] userRoles = SecurityService.getInstance(  ).getRolesByUser( user );
118 
119                 if ( userRoles != null )
120                 {
121                     filtersRole = new Filter[userRoles.length + 1];
122 
123                     for ( int i = 0; i < userRoles.length; i++ )
124                     {
125                         Query queryRole = new TermQuery( new Term( SearchItem.FIELD_ROLE, userRoles[i] ) );
126                         filtersRole[i] = new CachingWrapperFilter( new QueryWrapperFilter( queryRole ) );
127                     }
128                 }
129                 else
130                 {
131                     bFilterResult = true;
132                 }
133             }
134             else
135             {
136                 filtersRole = new Filter[1];
137             }
138 
139             if ( !bFilterResult )
140             {
141                 Query queryRole = new TermQuery( new Term( SearchItem.FIELD_ROLE, Page.ROLE_NONE ) );
142                 filtersRole[filtersRole.length - 1] = new CachingWrapperFilter( new QueryWrapperFilter( queryRole ) );
143                 listFilter.add( new ChainedFilter( filtersRole, ChainedFilter.OR ) );
144             }
145         }
146 
147         if ( StringUtils.isNotBlank( strDateAfter ) || StringUtils.isNotBlank( strDateBefore ) )
148         {
149             BytesRef strAfter = null;
150             BytesRef strBefore = null;
151 
152             if ( StringUtils.isNotBlank( strDateAfter ) )
153             {
154                 Date dateAfter = DateUtil.formatDate( strDateAfter, request.getLocale(  ) );
155                 strAfter = new BytesRef( DateTools.dateToString( dateAfter, Resolution.DAY ) );
156                 bDateAfter = true;
157             }
158 
159             if ( StringUtils.isNotBlank( strDateBefore ) )
160             {
161                 Date dateBefore = DateUtil.formatDate( strDateBefore, request.getLocale(  ) );
162                 strBefore = new BytesRef( DateTools.dateToString( dateBefore, Resolution.DAY ) );
163                 bDateBefore = true;
164             }
165 
166             Query queryDate = new TermRangeQuery( SearchItem.FIELD_DATE, strAfter, strBefore, bDateAfter, bDateBefore );
167             listFilter.add( new CachingWrapperFilter( new QueryWrapperFilter( queryDate ) ) );
168         }
169 
170         if ( ( typeFilter != null ) && ( typeFilter.length > 0 ) &&
171                 !typeFilter[0].equals( SearchService.TYPE_FILTER_NONE ) )
172         {
173             Filter[] filtersType = new Filter[typeFilter.length];
174 
175             for ( int i = 0; i < typeFilter.length; i++ )
176             {
177                 Query queryType = new TermQuery( new Term( SearchItem.FIELD_TYPE, typeFilter[i] ) );
178                 filtersType[i] = new CachingWrapperFilter( new QueryWrapperFilter( queryType ) );
179             }
180 
181             listFilter.add( new ChainedFilter( filtersType, ChainedFilter.OR ) );
182         }
183 
184         if ( !listFilter.isEmpty(  ) )
185         {
186             allFilter = new ChainedFilter( listFilter.toArray( new Filter[1] ), ChainedFilter.AND );
187         }
188 
189         try
190         {
191             IndexReader ir = DirectoryReader.open( IndexationService.getDirectoryIndex(  ) );
192             searcher = new IndexSearcher( ir );
193 
194             Query query = null;
195 
196             if ( StringUtils.isNotBlank( strTagFilter ) )
197             {
198                 BooleanQuery bQuery = new BooleanQuery(  );
199                 QueryParser parser = new QueryParser( IndexationService.LUCENE_INDEX_VERSION,
200                         SearchItem.FIELD_METADATA, IndexationService.getAnalyser(  ) );
201 
202                 Query queryMetaData = parser.parse( ( strQuery != null ) ? strQuery : "" );
203                 bQuery.add( queryMetaData, BooleanClause.Occur.SHOULD );
204 
205                 parser = new QueryParser( IndexationService.LUCENE_INDEX_VERSION, SearchItem.FIELD_SUMMARY,
206                         IndexationService.getAnalyser(  ) );
207 
208                 Query querySummary = parser.parse( ( strQuery != null ) ? strQuery : "" );
209                 bQuery.add( querySummary, BooleanClause.Occur.SHOULD );
210                 query = bQuery;
211             }
212             else
213             {
214                 QueryParser parser = new QueryParser( IndexationService.LUCENE_INDEX_VERSION,
215                         SearchItem.FIELD_CONTENTS, IndexationService.getAnalyser(  ) );
216 
217                 String operator = request.getParameter( PARAMETER_DEFAULT_OPERATOR );
218 
219                 if ( StringUtils.isNotEmpty( operator ) && operator.equals( PARAMETER_OPERATOR_AND ) )
220                 {
221                     parser.setDefaultOperator( QueryParser.AND_OPERATOR );
222                 }
223 
224                 query = parser.parse( ( StringUtils.isNotBlank( strQuery ) ) ? strQuery : "" );
225             }
226 
227             // Get results documents
228             TopDocs topDocs = searcher.search( query, allFilter, MAX_RESPONSES );
229             ScoreDoc[] hits = topDocs.scoreDocs;
230 
231             for ( int i = 0; i < hits.length; i++ )
232             {
233                 int docId = hits[i].doc;
234                 Document document = searcher.doc( docId );
235                 SearchItem si = new SearchItem( document );
236 
237                 if ( ( !bFilterResult ) || ( si.getRole(  ).equals( Page.ROLE_NONE ) ) ||
238                         ( SecurityService.getInstance(  ).isUserInRole( request, si.getRole(  ) ) ) )
239                 {
240                     listResults.add( si );
241                 }
242             }
243         }
244         catch ( Exception e )
245         {
246             AppLogService.error( e.getMessage(  ), e );
247         }
248 
249         return convertList( listResults );
250     }
251 
252     /**
253      * Convert a list of Lucene items into a list of generic search items
254      * @param listSource The list of Lucene items
255      * @return A list of generic search items
256      */
257     private List<SearchResult> convertList( List<SearchItem> listSource )
258     {
259         List<SearchResult> listDest = new ArrayList<SearchResult>(  );
260 
261         for ( SearchItem item : listSource )
262         {
263             SearchResult result = new SearchResult(  );
264             result.setId( item.getId(  ) );
265 
266             try
267             {
268                 result.setDate( DateTools.stringToDate( item.getDate(  ) ) );
269             }
270             catch ( ParseException e )
271             {
272                 AppLogService.error( "Bad Date Format for indexed item \"" + item.getTitle(  ) + "\" : " +
273                     e.getMessage(  ) );
274             }
275 
276             result.setUrl( item.getUrl(  ) );
277             result.setTitle( item.getTitle(  ) );
278             result.setSummary( item.getSummary(  ) );
279             result.setType( item.getType(  ) );
280             listDest.add( result );
281         }
282 
283         return listDest;
284     }
285 }