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.plugins.calendar.service.search;
35  
36  import fr.paris.lutece.plugins.calendar.service.Utils;
37  import fr.paris.lutece.plugins.calendar.web.Constants;
38  import fr.paris.lutece.portal.service.search.IndexationService;
39  import fr.paris.lutece.portal.service.search.SearchItem;
40  import fr.paris.lutece.portal.service.util.AppLogService;
41  import fr.paris.lutece.portal.service.util.AppPropertiesService;
42  
43  import java.text.ParseException;
44  import java.util.ArrayList;
45  import java.util.Collection;
46  import java.util.Date;
47  import java.util.List;
48  
49  import org.apache.commons.lang.StringUtils;
50  import org.apache.lucene.document.DateTools;
51  import org.apache.lucene.document.Document;
52  import org.apache.lucene.index.DirectoryReader;
53  import org.apache.lucene.index.IndexReader;
54  import org.apache.lucene.index.Term;
55  import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
56  import org.apache.lucene.search.BooleanClause;
57  import org.apache.lucene.search.BooleanQuery;
58  import org.apache.lucene.search.IndexSearcher;
59  import org.apache.lucene.search.PhraseQuery;
60  import org.apache.lucene.search.Query;
61  import org.apache.lucene.search.ScoreDoc;
62  import org.apache.lucene.search.TermQuery;
63  import org.apache.lucene.search.TermRangeQuery;
64  import org.apache.lucene.search.TopDocs;
65  import org.apache.lucene.util.BytesRef;
66  
67  
68  /**
69   * LuceneSearchEngine
70   */
71  public class CalendarLuceneSearchEngine implements CalendarSearchEngine
72  {
73      private static final String OPEN_PARENTHESIS = "(";
74      private static final String SPACE = " ";
75      private static final String CLOSE_PARENTHESIS = ")";
76      private static final String OR = "OR";
77      private static final String PROPERTY_RESULTS_LIMIT = "calendar.indexer.results.limit";
78  
79      /**
80       * Return search results
81       * @param arrayAgendaIds The calendar ids
82       * @param arrayCategory the category ids
83       * @param strContent The search query
84       * @param dateBegin The date begin
85       * @param dateEnd The date end
86       * @return Results as a collection of SearchResult
87       */
88      public List<CalendarSearchResult> getSearchResults( String[] arrayAgendaIds, String[] arrayCategory,
89              String strContent, Date dateBegin, Date dateEnd )
90      {
91          ArrayList<CalendarSearchItem> listResults = new ArrayList<CalendarSearchItem>( );
92  
93          if ( arrayAgendaIds == null || arrayAgendaIds.length == 0 )
94          {
95              return new ArrayList<CalendarSearchResult>( );
96          }
97          IndexSearcher searcher = null;
98  
99          //Filter filterRole = getFilterRoles( request );
100         Query filterRole = null;
101 
102         try
103         {
104             IndexReader ir = DirectoryReader.open( IndexationService.getDirectoryIndex( ) );
105             searcher = new IndexSearcher( ir );
106 
107             Collection<String> queriesForSearchInContent = new ArrayList<String>( );
108             Collection<String> queriesForSearchInTitle = new ArrayList<String>( );
109             Collection<String> fieldsForSearchInContent = new ArrayList<String>( );
110             Collection<String> fieldsForSearchInTitle = new ArrayList<String>( );
111             Collection<BooleanClause.Occur> flagsForSearchInContent = new ArrayList<BooleanClause.Occur>( );
112             Collection<BooleanClause.Occur> flagsForSearchInTitle = new ArrayList<BooleanClause.Occur>( );
113 
114             //Calendar Id
115             if ( arrayAgendaIds.length > 0 )
116             {
117                 String strQueryCalendar = OPEN_PARENTHESIS;
118                 int intMoreCalendar = 0;
119 
120                 for ( String strAgendaId : arrayAgendaIds )
121                 {
122                     strQueryCalendar += ( strAgendaId + "_" + Constants.CALENDAR_SHORT_NAME );
123                     ++intMoreCalendar;
124 
125                     if ( ( arrayAgendaIds.length > 1 ) && ( intMoreCalendar < arrayAgendaIds.length ) )
126                     {
127                         strQueryCalendar += ( SPACE + OR + SPACE );
128                     }
129                 }
130 
131                 strQueryCalendar += CLOSE_PARENTHESIS;
132 
133                 Query queryAgendaId = new TermQuery( new Term( Constants.FIELD_CALENDAR_ID, strQueryCalendar ) );
134                 queriesForSearchInContent.add( queryAgendaId.toString( ) );
135                 queriesForSearchInTitle.add( queryAgendaId.toString( ) );
136                 fieldsForSearchInContent.add( Constants.FIELD_CALENDAR_ID );
137                 flagsForSearchInContent.add( BooleanClause.Occur.MUST );
138                 fieldsForSearchInTitle.add( Constants.FIELD_CALENDAR_ID );
139                 flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
140             }
141 
142             //category Id
143             if ( ( arrayCategory != null ) && ( arrayCategory.length > 0 ) )
144             {
145                 String strQueryCategory = OPEN_PARENTHESIS;
146                 int intMoreCategory = 0;
147 
148                 for ( String strCategoryId : arrayCategory )
149                 {
150                     strQueryCategory += strCategoryId;
151                     ++intMoreCategory;
152 
153                     if ( ( arrayCategory.length > 1 ) && ( intMoreCategory < arrayCategory.length ) )
154                     {
155                         strQueryCategory += ( SPACE + OR + SPACE );
156                     }
157                 }
158 
159                 strQueryCategory += CLOSE_PARENTHESIS;
160 
161                 Query queryAgendaId = new TermQuery( new Term( Constants.FIELD_CATEGORY, strQueryCategory ) );
162                 queriesForSearchInContent.add( queryAgendaId.toString( ) );
163                 queriesForSearchInTitle.add( queryAgendaId.toString( ) );
164                 fieldsForSearchInContent.add( Constants.FIELD_CATEGORY );
165                 flagsForSearchInContent.add( BooleanClause.Occur.MUST );
166                 fieldsForSearchInTitle.add( Constants.FIELD_CATEGORY );
167                 flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
168             }
169 
170             //Type (=calendar)
171             PhraseQuery.Builder queryTypeBuilder = new PhraseQuery.Builder( );
172             queryTypeBuilder.add( new Term( SearchItem.FIELD_TYPE, Constants.PLUGIN_NAME ) );
173             PhraseQuery queryType = queryTypeBuilder.build( );
174             queriesForSearchInContent.add( queryType.toString( ) );
175             queriesForSearchInTitle.add( queryType.toString( ) );
176             fieldsForSearchInContent.add( SearchItem.FIELD_TYPE );
177             flagsForSearchInContent.add( BooleanClause.Occur.MUST );
178             fieldsForSearchInTitle.add( SearchItem.FIELD_TYPE );
179             flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
180 
181             //Content
182             if ( StringUtils.isNotBlank( strContent ) )
183             {
184                 Query queryContent = new TermQuery( new Term( SearchItem.FIELD_CONTENTS, strContent ) );
185                 queriesForSearchInTitle.add( queryContent.toString( ) );
186                 fieldsForSearchInContent.add( SearchItem.FIELD_CONTENTS );
187                 flagsForSearchInContent.add( BooleanClause.Occur.MUST );
188 
189                 Query queryTitle = new TermQuery( new Term( SearchItem.FIELD_TITLE, strContent ) );
190                 queriesForSearchInContent.add( queryTitle.toString( ) );
191                 fieldsForSearchInTitle.add( SearchItem.FIELD_TITLE );
192                 flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
193             }
194 
195             //Dates
196             if ( ( dateBegin != null ) && ( dateEnd != null ) )
197             {
198                 BytesRef strDateBegin = new BytesRef( Utils.getDate( dateBegin ) );
199                 BytesRef strDateEnd = new BytesRef( Utils.getDate( dateEnd ) );
200                 Query queryDate = new TermRangeQuery( SearchItem.FIELD_DATE, strDateBegin, strDateEnd, true, true );
201                 queriesForSearchInContent.add( queryDate.toString( ) );
202                 queriesForSearchInTitle.add( queryDate.toString( ) );
203                 fieldsForSearchInContent.add( SearchItem.FIELD_DATE );
204                 flagsForSearchInContent.add( BooleanClause.Occur.MUST );
205                 fieldsForSearchInTitle.add( SearchItem.FIELD_DATE );
206                 flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
207             }else{
208             	 BytesRef strDate = new BytesRef( Utils.getDate( new Date()) );
209                  Query queryDate = new TermRangeQuery( SearchItem.FIELD_DATE, strDate ,null, true, true );
210                  queriesForSearchInContent.add( queryDate.toString( ) );
211                  queriesForSearchInTitle.add( queryDate.toString( ) );
212                  fieldsForSearchInContent.add( SearchItem.FIELD_DATE );
213                  flagsForSearchInContent.add( BooleanClause.Occur.MUST );
214                  fieldsForSearchInTitle.add( SearchItem.FIELD_DATE );
215                  flagsForSearchInTitle.add( BooleanClause.Occur.MUST );
216             	
217             }
218 
219             //Search in contents
220             Query queryMulti = MultiFieldQueryParser.parse(
221                     queriesForSearchInContent.toArray( new String[queriesForSearchInContent.size( )] ),
222                     fieldsForSearchInContent.toArray( new String[fieldsForSearchInContent.size( )] ),
223                     flagsForSearchInContent.toArray( new BooleanClause.Occur[flagsForSearchInContent.size( )] ),
224                     IndexationService.getAnalyser( ) );
225 
226             // Get results documents
227             TopDocs hits = null;
228 
229             int nLimit = Integer.parseInt( AppPropertiesService.getProperty( PROPERTY_RESULTS_LIMIT ) );
230 
231             if ( filterRole != null ) {
232                 BooleanQuery.Builder bQueryBuilder = new BooleanQuery.Builder( );
233                 bQueryBuilder.add( queryMulti, BooleanClause.Occur.MUST );
234                 bQueryBuilder.add( filterRole, BooleanClause.Occur.FILTER );
235                 queryMulti = bQueryBuilder.build( );
236             }
237 
238             hits = searcher.search( queryMulti, nLimit );
239 
240             for ( int i = 0; hits.totalHits > i; i++ )
241             {
242                 ScoreDoc hit = hits.scoreDocs[i];
243                 Document document = searcher.doc( hit.doc );
244                 CalendarSearchItem si = new CalendarSearchItem( document );
245                 listResults.add( si );
246             }
247 
248             //Search in titles
249             Query queryMultiTitle = MultiFieldQueryParser.parse(
250                     queriesForSearchInTitle.toArray( new String[queriesForSearchInTitle.size( )] ),
251                     fieldsForSearchInTitle.toArray( new String[fieldsForSearchInTitle.size( )] ),
252                     flagsForSearchInTitle.toArray( new BooleanClause.Occur[flagsForSearchInTitle.size( )] ),
253                     IndexationService.getAnalyser( ) );
254 
255             // Get results documents
256             TopDocs hitsTitle = null;
257 
258             if ( filterRole != null ) {
259                 BooleanQuery.Builder bQueryBuilderTitle = new BooleanQuery.Builder( );
260                 bQueryBuilderTitle.add( queryMultiTitle, BooleanClause.Occur.MUST );
261                 bQueryBuilderTitle.add( filterRole, BooleanClause.Occur.FILTER );
262                 queryMultiTitle = bQueryBuilderTitle.build( );
263             }
264 
265             hitsTitle = searcher.search( queryMultiTitle, nLimit );
266 
267             for ( int i = 0; hitsTitle.totalHits > i; i++ )
268             {
269                 ScoreDoc hit = hitsTitle.scoreDocs[i];
270                 Document document = searcher.doc( hit.doc );
271                 CalendarSearchItem si = new CalendarSearchItem( document );
272                 listResults.add( si );
273             }
274         }
275         catch ( Exception e )
276         {
277             AppLogService.error( e.getMessage( ), e );
278         }
279 
280         return convertList( listResults );
281     }
282 
283     /**
284      * Convert the SearchItem list on SearchResult list
285      * @param listSource The source list
286      * @return The result list
287      */
288     private List<CalendarSearchResult> convertList( List<CalendarSearchItem> listSource )
289     {
290         List<CalendarSearchResult> listDest = new ArrayList<CalendarSearchResult>( );
291 
292         for ( CalendarSearchItem item : listSource )
293         {
294             CalendarSearchResult result = new CalendarSearchResult( );
295             result.setId( item.getId( ) );
296 
297             try
298             {
299                 result.setDate( DateTools.stringToDate( item.getDate( ) ) );
300             }
301             catch ( ParseException e )
302             {
303                 AppLogService.error( "Bad Date Format for indexed item \"" + item.getTitle( ) + "\" : "
304                         + e.getMessage( ) );
305             }
306 
307             result.setUrl( item.getUrl( ) );
308             result.setTitle( item.getTitle( ) );
309             result.setSummary( item.getSummary( ) );
310             result.setType( item.getType( ) );
311             result.setHtmlSummary( item.getHtmlSummary( ) );
312             listDest.add( result );
313         }
314 
315         return listDest;
316     }
317 }