View Javadoc
1   /*
2    * Copyright (c) 2002-2017, 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.directory.service.directorysearch;
35  
36  import java.io.IOException;
37  import java.util.ArrayList;
38  import java.util.Date;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map.Entry;
42  
43  import javax.servlet.http.HttpServletRequest;
44  
45  import org.apache.commons.collections.CollectionUtils;
46  import org.apache.commons.lang3.mutable.MutableBoolean;
47  import org.apache.lucene.index.IndexWriter;
48  import org.apache.lucene.search.IndexSearcher;
49  import org.json.JSONException;
50  import org.json.JSONObject;
51  
52  import fr.paris.lutece.plugins.directory.business.Directory;
53  import fr.paris.lutece.plugins.directory.business.DirectoryHome;
54  import fr.paris.lutece.plugins.directory.business.EntryTypeArray;
55  import fr.paris.lutece.plugins.directory.business.IndexerAction;
56  import fr.paris.lutece.plugins.directory.business.IndexerActionFilter;
57  import fr.paris.lutece.plugins.directory.business.IndexerActionHome;
58  import fr.paris.lutece.plugins.directory.business.Record;
59  import fr.paris.lutece.plugins.directory.business.RecordField;
60  import fr.paris.lutece.plugins.directory.business.RecordFieldFilter;
61  import fr.paris.lutece.plugins.directory.business.RecordFieldHome;
62  import fr.paris.lutece.plugins.directory.service.record.IRecordService;
63  import fr.paris.lutece.plugins.directory.service.record.RecordService;
64  import fr.paris.lutece.plugins.directory.utils.DirectoryUtils;
65  import fr.paris.lutece.portal.service.plugin.Plugin;
66  import fr.paris.lutece.portal.service.plugin.PluginService;
67  import fr.paris.lutece.portal.service.spring.SpringContextService;
68  import fr.paris.lutece.portal.service.util.AppLogService;
69  
70  /**
71   * DirectorySearchService
72   */
73  public class DirectorySearchService
74  {
75      private static final String BEAN_SEARCH_ENGINE = "directorySearchEngine";
76      private static final String PARAMETER_ID_DIRECTORY = "id_directory";
77      private static final String PARAMETER_ID_ENTRY = "id_entry";
78      private static final String JSON_QUERY_RESULT = "query";
79  
80      private static IDirectorySearchIndexer _indexer;
81      private static DirectorySearchFactory _directorySearchFactory;
82      private static final int CONSTANT_TIME_CORRECTION = 3600000 * 12;
83  
84      // Constants corresponding to the variables defined in the lutece.properties file
85      private static DirectorySearchService _singleton;
86  
87      /**
88       * Creates a new instance of DirectorySearchService
89       */
90      public DirectorySearchService( )
91      {
92          _indexer = SpringContextService.getBean( "directoryIndexer" );
93          _directorySearchFactory = DirectorySearchFactory.getInstance( );
94      }
95  
96      /**
97       *
98       * @return singleton
99       */
100     public static DirectorySearchService getInstance( )
101     {
102         if ( _singleton == null )
103         {
104             _singleton = new DirectorySearchService( );
105         }
106 
107         return _singleton;
108     }
109 
110     /**
111      * Return a list of record key return by the search
112      * 
113      * @param directory
114      *            the directory
115      * @param mapSearch
116      *            a map which contains for each entry the list of recorField(value of field search) associate
117      * @param dateCreation
118      *            the creation date
119      * @param dateCreationBegin
120      *            the date begin to search for the creation date
121      * @param dateCreationEnd
122      *            the date end to search for the creation date
123      * @param filter
124      *            the filter
125      * @param plugin
126      *            the plugin
127      * @return a list of record key return by the search
128      */
129     public List<Integer> getSearchResults( Directory directory, HashMap<String, List<RecordField>> mapSearch, Date dateCreation, Date dateCreationBegin,
130             Date dateCreationEnd, RecordFieldFilter filter, Plugin plugin )
131     {
132         return getSearchResults( directory, mapSearch, dateCreationEnd, dateCreationBegin, dateCreationEnd, null, null, null, filter, plugin );
133     }
134 
135     /**
136      * Return a list of record key return by the search
137      * 
138      * @param directory
139      *            the directory
140      * @param mapSearch
141      *            a map which contains for each entry the list of recorField(value of field search) associate
142      * @param dateCreation
143      *            the creation date
144      * @param dateCreationBegin
145      *            the date begin to search for the creation date
146      * @param dateCreationEnd
147      *            the date end to search for the creation date
148      * @param dateModification
149      *            the modification date
150      * @param dateModificationBegin
151      *            the date begin to search for the modification date
152      * @param dateModificationEnd
153      *            the date end to search for the modification date
154      * @param filter
155      *            the filter
156      * @param plugin
157      *            the plugin
158      * @return a list of record key return by the search
159      */
160     public List<Integer> getSearchResults( Directory directory, HashMap<String, List<RecordField>> mapSearch, Date dateCreation, Date dateCreationBegin,
161             Date dateCreationEnd, Date dateModification, Date dateModificationBegin, Date dateModificationEnd, RecordFieldFilter filter, Plugin plugin )
162     {
163         List<Integer> listRecordResult = new ArrayList<Integer>( );
164 
165         IRecordService recordService = SpringContextService.getBean( RecordService.BEAN_SERVICE );
166         listRecordResult = recordService.getListRecordId( filter, plugin );
167 
168         if ( mapSearch != null )
169         {
170             List<Integer> listRecordResultTmp = null;
171             List<RecordField> recordFieldSearch;
172             HashMap<String, Object> mapSearchItemEntry;
173             boolean bSearchRecordEmpty;
174             boolean bSearchEmpty;
175 
176             try
177             {
178                 IDirectorySearchEngine engine = SpringContextService.getBean( BEAN_SEARCH_ENGINE );
179 
180                 listRecordResultTmp = new ArrayList<Integer>( );
181                 bSearchEmpty = true;
182 
183                 for ( Object entryMapSearch : mapSearch.entrySet( ) )
184                 {
185                     recordFieldSearch = ( (Entry<String, List<RecordField>>) entryMapSearch ).getValue( );
186 
187                     int nIdEntry = DirectoryUtils.convertStringToInt( ( (Entry<String, List<RecordField>>) entryMapSearch ).getKey( ) );
188                     bSearchRecordEmpty = true;
189 
190                     if ( recordFieldSearch != null )
191                     {
192                         mapSearchItemEntry = new HashMap<String, Object>( );
193 
194                         boolean bIsArray = false;
195                         boolean bFirstRecord = true;
196 
197                         for ( RecordField recordField : recordFieldSearch )
198                         {
199                             if ( recordField.getEntry( ) instanceof EntryTypeArray )
200                             {
201                                 // for array, we do a search on content for each case
202                                 bIsArray = true;
203                                 mapSearchItemEntry = new HashMap<String, Object>( );
204                                 recordField.getEntry( ).addSearchCriteria( mapSearchItemEntry, recordField );
205 
206                                 if ( mapSearchItemEntry.size( ) > 0 )
207                                 {
208                                     bSearchRecordEmpty = false;
209                                     bSearchEmpty = false;
210                                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
211                                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY_ENTRY, nIdEntry );
212 
213                                     List<Integer> ids = engine.getSearchResults( mapSearchItemEntry );
214 
215                                     if ( CollectionUtils.isEmpty( ids ) )
216                                     {
217                                         listRecordResultTmp = new ArrayList<Integer>( );
218 
219                                         break;
220                                     }
221                                     else
222                                         if ( bFirstRecord )
223                                         {
224                                             listRecordResultTmp = ids;
225                                             bFirstRecord = false;
226                                         }
227                                         else
228                                         {
229                                             listRecordResultTmp = (List<Integer>) CollectionUtils.intersection( listRecordResultTmp, ids );
230                                         }
231                                 }
232                             }
233                             else
234                             {
235                                 recordField.getEntry( ).addSearchCriteria( mapSearchItemEntry, recordField );
236                             }
237                         }
238 
239                         if ( !bIsArray && ( mapSearchItemEntry.size( ) > 0 ) )
240                         {
241                             bSearchRecordEmpty = false;
242                             bSearchEmpty = false;
243                             mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
244                             mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY_ENTRY, nIdEntry );
245                             listRecordResultTmp.addAll( engine.getSearchResults( mapSearchItemEntry ) );
246                         }
247 
248                         if ( !bSearchRecordEmpty && !directory.isSearchOperatorOr( ) )
249                         {
250                             // keeping order is important for display
251                             listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
252                             listRecordResultTmp = new ArrayList<Integer>( );
253                         }
254                     }
255                 }
256 
257                 if ( directory.isSearchOperatorOr( ) && !bSearchEmpty )
258                 {
259                     listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
260                 }
261 
262                 // date creation of a record
263                 if ( dateCreation != null )
264                 {
265                     listRecordResultTmp = new ArrayList<Integer>( );
266                     mapSearchItemEntry = new HashMap<String, Object>( );
267                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
268                     dateCreation.setTime( dateCreation.getTime( ) + CONSTANT_TIME_CORRECTION );
269 
270                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_CREATION, dateCreation );
271                     listRecordResultTmp.addAll( engine.getSearchResults( mapSearchItemEntry ) );
272 
273                     // keeping order is important for display
274                     listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
275                 }
276                 else
277                     if ( ( dateCreationBegin != null ) && ( dateCreationEnd != null ) )
278                     {
279                         dateCreationBegin.setTime( dateCreationBegin.getTime( ) + CONSTANT_TIME_CORRECTION );
280                         dateCreationEnd.setTime( dateCreationEnd.getTime( ) + CONSTANT_TIME_CORRECTION );
281 
282                         listRecordResultTmp = new ArrayList<Integer>( );
283                         mapSearchItemEntry = new HashMap<String, Object>( );
284                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
285                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_CREATION_BEGIN, dateCreationBegin );
286                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_CREATION_END, dateCreationEnd );
287                         listRecordResultTmp.addAll( engine.getSearchResults( mapSearchItemEntry ) );
288 
289                         // keeping order is important for display
290                         listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
291                     }
292 
293                 // date modification of a record
294                 if ( dateModification != null )
295                 {
296                     listRecordResultTmp = new ArrayList<Integer>( );
297                     mapSearchItemEntry = new HashMap<String, Object>( );
298                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
299                     dateModification.setTime( dateModification.getTime( ) + CONSTANT_TIME_CORRECTION );
300 
301                     mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_MODIFICATION, dateModification );
302                     listRecordResultTmp.addAll( engine.getSearchResults( mapSearchItemEntry ) );
303 
304                     // keeping order is important for display
305                     listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
306                 }
307                 else
308                     if ( ( dateModificationBegin != null ) && ( dateModificationEnd != null ) )
309                     {
310                         dateModificationBegin.setTime( dateModificationBegin.getTime( ) + CONSTANT_TIME_CORRECTION );
311                         dateModificationEnd.setTime( dateModificationEnd.getTime( ) + CONSTANT_TIME_CORRECTION );
312 
313                         listRecordResultTmp = new ArrayList<Integer>( );
314                         mapSearchItemEntry = new HashMap<String, Object>( );
315                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, directory.getIdDirectory( ) );
316                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_MODIFICATION_BEGIN, dateModificationBegin );
317                         mapSearchItemEntry.put( DirectorySearchItem.FIELD_DATE_MODIFICATION_END, dateModificationEnd );
318                         listRecordResultTmp.addAll( engine.getSearchResults( mapSearchItemEntry ) );
319 
320                         // keeping order is important for display
321                         listRecordResult = DirectoryUtils.retainAllIdsKeepingFirstOrder( listRecordResult, listRecordResultTmp );
322                     }
323             }
324             catch( Exception e )
325             {
326                 AppLogService.error( e.getMessage( ), e );
327                 // If an error occurred clean result list
328                 listRecordResult = new ArrayList<Integer>( );
329             }
330         }
331 
332         return listRecordResult;
333     }
334 
335     public String getAutocompleteResult( HttpServletRequest request ) throws Exception
336     {
337         String strIdDirectory = request.getParameter( PARAMETER_ID_DIRECTORY );
338         String strIdEntry = request.getParameter( PARAMETER_ID_ENTRY );
339         Plugin plugin = PluginService.getPlugin( "directory" );
340         StringBuffer result = new StringBuffer( );
341 
342         IDirectorySearchEngine engine = SpringContextService.getBean( BEAN_SEARCH_ENGINE );
343 
344         if ( ( ( strIdDirectory != null ) && !strIdDirectory.equals( DirectoryUtils.EMPTY_STRING ) )
345                 && ( ( strIdEntry != null ) && !strIdEntry.equals( DirectoryUtils.EMPTY_STRING ) ) )
346         {
347             HashMap<String, Object> mapSearchItemEntry = new HashMap<String, Object>( );
348             mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY, Integer.parseInt( strIdDirectory ) );
349             mapSearchItemEntry.put( DirectorySearchItem.FIELD_ID_DIRECTORY_ENTRY, Integer.parseInt( strIdEntry ) );
350 
351             List<Integer> listIntRecordField = engine.getSearchResults( mapSearchItemEntry );
352             List<RecordField> listRecordField = new ArrayList<RecordField>( );
353 
354             for ( Integer idDirectoryRecord : listIntRecordField )
355             {
356                 RecordFieldFilter recordFilter = new RecordFieldFilter( );
357                 recordFilter.setIdDirectory( Integer.parseInt( strIdDirectory ) );
358                 recordFilter.setIdEntry( Integer.parseInt( strIdEntry ) );
359                 recordFilter.setIdRecord( idDirectoryRecord );
360                 listRecordField.addAll( RecordFieldHome.getRecordFieldList( recordFilter, plugin ) );
361             }
362 
363             HashMap<String, String> mapResult = new HashMap<String, String>( );
364 
365             for ( RecordField recordField : listRecordField )
366             {
367                 // result.append( recordField.getValue( ) + "%" );
368                 mapResult.put( recordField.getValue( ), recordField.getValue( ) );
369             }
370 
371             for ( String key : mapResult.keySet( ) )
372             {
373                 result.append( key + "%" );
374             }
375         }
376 
377         JSONObject jo = new JSONObject( );
378 
379         try
380         {
381             jo.put( JSON_QUERY_RESULT, result.toString( ) );
382         }
383         catch( JSONException e )
384         {
385             AppLogService.error( e.getMessage( ), e );
386         }
387 
388         return jo.toString( );
389     }
390 
391     /**
392      * Process indexing
393      * 
394      * @param bCreate
395      *            true for start full indexing false for begin incremental indexing
396      * @return the log
397      */
398     public String processIndexing( boolean bCreate )
399     {
400         StringBuffer sbLogs = new StringBuffer( );
401         IndexWriter writer = null;
402         MutableBoolean bCreateIndex = new MutableBoolean( bCreate );
403 
404         try
405         {
406             sbLogs.append( "\r\nIndexing all contents ...\r\n" );
407             writer = _directorySearchFactory.getIndexWriter( bCreateIndex );
408 
409             Date start = new Date( );
410 
411             sbLogs.append( "\r\n<strong>Indexer : " );
412             sbLogs.append( _indexer.getName( ) );
413             sbLogs.append( " - " );
414             sbLogs.append( _indexer.getDescription( ) );
415             sbLogs.append( "</strong>\r\n" );
416             _indexer.processIndexing( writer, bCreateIndex.getValue( ), sbLogs );
417 
418             Date end = new Date( );
419 
420             sbLogs.append( "Duration of the treatment : " );
421             sbLogs.append( end.getTime( ) - start.getTime( ) );
422             sbLogs.append( " milliseconds\r\n" );
423         }
424         catch( Exception e )
425         {
426             sbLogs.append( " caught a " );
427             sbLogs.append( e.getClass( ) );
428             sbLogs.append( "\n with message: " );
429             sbLogs.append( e.getMessage( ) );
430             sbLogs.append( "\r\n" );
431             AppLogService.error( "Indexing error : " + e.getMessage( ), e );
432         }
433         finally
434         {
435             try
436             {
437                 if ( writer != null )
438                 {
439                     writer.close( );
440                 }
441             }
442             catch( IOException e )
443             {
444                 AppLogService.error( e.getMessage( ), e );
445             }
446         }
447 
448         return sbLogs.toString( );
449     }
450 
451     /**
452      * Add Indexer Action to perform on a record
453      * 
454      * @param nIdRecord
455      *            the id of the record
456      * @param nIdTask
457      *            the key of the action to do
458      * @param plugin
459      *            the plugin
460      */
461     public void addIndexerAction( int nIdRecord, int nIdTask, Plugin plugin )
462     {
463         IRecordService recordService = SpringContextService.getBean( RecordService.BEAN_SERVICE );
464         Record record = recordService.findByPrimaryKey( nIdRecord, plugin );
465 
466         if ( ( record != null ) && ( record.getDirectory( ) != null ) )
467         {
468             int nDirectoryId = record.getDirectory( ).getIdDirectory( );
469             Directory directory = DirectoryHome.findByPrimaryKey( nDirectoryId, plugin );
470 
471             if ( ( directory != null ) && directory.isIndexed( ) )
472             {
473                 IndexerAction indexerAction = new IndexerAction( );
474                 indexerAction.setIdRecord( nIdRecord );
475                 indexerAction.setIdTask( nIdTask );
476                 IndexerActionHome.create( indexerAction, plugin );
477             }
478         }
479     }
480 
481     /**
482      * Remove a Indexer Action
483      * 
484      * @param nIdAction
485      *            the key of the action to remove
486      * @param plugin
487      *            the plugin
488      */
489     public void removeIndexerAction( int nIdAction, Plugin plugin )
490     {
491         IndexerActionHome.remove( nIdAction, plugin );
492     }
493 
494     /**
495      * return a list of IndexerAction by task key
496      * 
497      * @param nIdTask
498      *            the task kety
499      * @param plugin
500      *            the plugin
501      * @return a list of IndexerAction
502      */
503     public List<IndexerAction> getAllIndexerActionByTask( int nIdTask, Plugin plugin )
504     {
505         IndexerActionFilter filter = new IndexerActionFilter( );
506         filter.setIdTask( nIdTask );
507 
508         return IndexerActionHome.getList( filter, plugin );
509     }
510 
511     /**
512      * return searcher
513      * 
514      * @return searcher
515      * @throws IOException
516      *             - if there is a low-level IO error
517      */
518     public IndexSearcher getSearcher( ) throws IOException
519     {
520         return _directorySearchFactory.getIndexSearcher( );
521     }
522 }