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 fr.paris.lutece.plugins.directory.business.Directory;
37  import fr.paris.lutece.plugins.directory.business.DirectoryFilter;
38  import fr.paris.lutece.plugins.directory.business.DirectoryHome;
39  import fr.paris.lutece.plugins.directory.business.IndexerAction;
40  import fr.paris.lutece.plugins.directory.business.Record;
41  import fr.paris.lutece.plugins.directory.business.RecordField;
42  import fr.paris.lutece.plugins.directory.business.RecordFieldFilter;
43  import fr.paris.lutece.plugins.directory.business.RecordFieldHome;
44  import fr.paris.lutece.plugins.directory.service.DirectoryPlugin;
45  import fr.paris.lutece.plugins.directory.service.record.IRecordService;
46  import fr.paris.lutece.plugins.directory.service.record.RecordService;
47  import fr.paris.lutece.plugins.directory.utils.DirectoryUtils;
48  import fr.paris.lutece.portal.service.message.SiteMessageException;
49  import fr.paris.lutece.portal.service.plugin.Plugin;
50  import fr.paris.lutece.portal.service.plugin.PluginService;
51  import fr.paris.lutece.portal.service.spring.SpringContextService;
52  import fr.paris.lutece.portal.service.util.AppPropertiesService;
53  
54  import org.apache.lucene.document.DateTools;
55  import org.apache.lucene.document.Document;
56  import org.apache.lucene.document.Field;
57  import org.apache.lucene.document.FieldType;
58  import org.apache.lucene.document.StringField;
59  import org.apache.lucene.document.TextField;
60  import org.apache.lucene.index.CorruptIndexException;
61  import org.apache.lucene.index.IndexWriter;
62  import org.apache.lucene.index.Term;
63  
64  import java.io.IOException;
65  
66  import java.util.ArrayList;
67  import java.util.Date;
68  import java.util.HashMap;
69  import java.util.Iterator;
70  import java.util.List;
71  
72  /**
73   * Directory Indexer
74   *
75   */
76  public class DirectoryIndexer implements IDirectorySearchIndexer
77  {
78      private static final int LIST_RECORD_STEP = 50;
79      private static final String ENABLE_VALUE_TRUE = "1";
80      private static final String PROPERTY_INDEXER_DESCRIPTION = "directory.internalIndexer.description";
81      private static final String PROPERTY_INDEXER_NAME = "directory.internalIndexer.name";
82      private static final String PROPERTY_INDEXER_VERSION = "directory.internalIndexer.version";
83      private static final String PROPERTY_INDEXER_ENABLE = "directory.internalIndexer.enable";
84      private static final List<Integer> _lListIdRecordToDelete = new ArrayList<Integer>( );
85  
86      /**
87       * {@inheritDoc}
88       */
89      @Override
90      public String getDescription( )
91      {
92          return AppPropertiesService.getProperty( PROPERTY_INDEXER_DESCRIPTION );
93      }
94  
95      /**
96       * Index given list of record
97       * 
98       * @param indexWriter
99       *            the indexWriter
100      * @param lListId
101      *            list of id directory / list of id record
102      * @param plugin
103      *            the plugin
104      * @return new empty list of id directory / list of id record
105      * @throws CorruptIndexException
106      * @throws IOException
107      * @throws InterruptedException
108      */
109     private HashMap<Integer, List<Integer>> indexListRecord( IndexWriter indexWriter, HashMap<Integer, List<Integer>> lListId, Plugin plugin )
110             throws CorruptIndexException, IOException, InterruptedException
111     {
112         Iterator<Integer> it = lListId.keySet( ).iterator( );
113         IRecordService recordService = SpringContextService.getBean( RecordService.BEAN_SERVICE );
114 
115         while ( it.hasNext( ) )
116         {
117             Integer nDirectoryId = it.next( );
118             Directory directory = DirectoryHome.findByPrimaryKey( nDirectoryId, plugin );
119             List<Integer> lListRecordId = lListId.get( nDirectoryId );
120 
121             int nListRecordSize = lListRecordId.size( );
122 
123             if ( nListRecordSize > LIST_RECORD_STEP )
124             {
125                 int nIndex = 0;
126                 int nMax = nListRecordSize - LIST_RECORD_STEP;
127 
128                 for ( int i = 0; i < nMax; i += LIST_RECORD_STEP )
129                 {
130                     List<RecordField> lListrecordField = RecordFieldHome.getRecordFieldListByRecordIdList( lListRecordId.subList( i, i + LIST_RECORD_STEP ),
131                             plugin );
132 
133                     for ( RecordField recordField : lListrecordField )
134                     {
135                         indexWriter.addDocument( getDocument( recordField, recordField.getRecord( ), directory ) );
136                     }
137 
138                     for ( Record record : recordService.loadListByListId( lListRecordId.subList( i, i + LIST_RECORD_STEP ), plugin ) )
139                     {
140                         indexWriter.addDocument( getDocument( record, directory ) );
141                     }
142 
143                     nIndex = i;
144                 }
145 
146                 nIndex += LIST_RECORD_STEP;
147 
148                 List<RecordField> lListrecordField = RecordFieldHome
149                         .getRecordFieldListByRecordIdList( lListRecordId.subList( nIndex, nListRecordSize ), plugin );
150 
151                 for ( RecordField recordField : lListrecordField )
152                 {
153                     indexWriter.addDocument( getDocument( recordField, recordField.getRecord( ), directory ) );
154                 }
155 
156                 for ( Record record : recordService.loadListByListId( lListRecordId.subList( nIndex, nListRecordSize ), plugin ) )
157                 {
158                     indexWriter.addDocument( getDocument( record, directory ) );
159                 }
160             }
161             else
162             {
163                 List<RecordField> lListrecordField = RecordFieldHome.getRecordFieldListByRecordIdList( lListRecordId, plugin );
164 
165                 for ( RecordField recordField : lListrecordField )
166                 {
167                     indexWriter.addDocument( getDocument( recordField, recordField.getRecord( ), directory ) );
168                 }
169 
170                 for ( Record record : recordService.loadListByListId( lListRecordId, plugin ) )
171                 {
172                     indexWriter.addDocument( getDocument( record, directory ) );
173                 }
174             }
175         }
176 
177         return new HashMap<Integer, List<Integer>>( );
178     }
179 
180     /**
181      * Append key to list of id directory / list of id record
182      * 
183      * @param nIdDirectory
184      *            the directory id
185      * @param nIdAction
186      *            the action id
187      * @param hm
188      *            current list of id directory / list of id record
189      * @return list of id directory / list of id record
190      */
191     private HashMap<Integer, List<Integer>> appendKey( Integer nIdDirectory, Integer nIdAction, HashMap<Integer, List<Integer>> hm )
192     {
193         if ( hm.containsKey( nIdDirectory ) )
194         {
195             hm.get( nIdDirectory ).add( nIdAction );
196         }
197         else
198         {
199             List<Integer> lListIdRecord = new ArrayList<Integer>( );
200             lListIdRecord.add( nIdAction );
201             hm.put( nIdDirectory, lListIdRecord );
202         }
203 
204         return hm;
205     }
206 
207     /**
208      * {@inheritDoc}
209      */
210     @Override
211     public void processIndexing( IndexWriter indexWriter, boolean bCreate, StringBuffer sbLogs ) throws IOException, InterruptedException, SiteMessageException
212     {
213         Plugin plugin = PluginService.getPlugin( DirectoryPlugin.PLUGIN_NAME );
214         RecordFieldFilter recordFieldFilter = new RecordFieldFilter( );
215         HashMap<Integer, List<Integer>> hm = new HashMap<Integer, List<Integer>>( );
216         IRecordService recordService = SpringContextService.getBean( RecordService.BEAN_SERVICE );
217 
218         if ( !bCreate )
219         {
220             // incremental indexing
221             // delete all record which must be delete
222             for ( IndexerAction action : DirectorySearchService.getInstance( ).getAllIndexerActionByTask( IndexerAction.TASK_DELETE, plugin ) )
223             {
224                 sbLogRecord( sbLogs, action.getIdRecord( ), DirectoryUtils.CONSTANT_ID_NULL, IndexerAction.TASK_DELETE );
225                 indexWriter.deleteDocuments( new Term( DirectorySearchItem.FIELD_ID_DIRECTORY_RECORD, Integer.toString( action.getIdRecord( ) ) ) );
226                 DirectorySearchService.getInstance( ).removeIndexerAction( action.getIdAction( ), plugin );
227             }
228 
229             // Hack : see this.appendListRecordToDelete() comments
230             for ( Integer nIdRecord : _lListIdRecordToDelete )
231             {
232                 indexWriter.deleteDocuments( new Term( DirectorySearchItem.FIELD_ID_DIRECTORY_RECORD, Integer.toString( nIdRecord ) ) );
233             }
234 
235             _lListIdRecordToDelete.clear( );
236 
237             // Update all record which must be update
238             for ( IndexerAction action : DirectorySearchService.getInstance( ).getAllIndexerActionByTask( IndexerAction.TASK_MODIFY, plugin ) )
239             {
240                 Integer nDirectoryId = recordService.getDirectoryIdByRecordId( Integer.valueOf( action.getIdRecord( ) ), plugin );
241 
242                 if ( nDirectoryId != null )
243                 {
244                     sbLogRecord( sbLogs, action.getIdRecord( ), nDirectoryId.intValue( ), IndexerAction.TASK_MODIFY );
245 
246                     indexWriter.deleteDocuments( new Term( DirectorySearchItem.FIELD_ID_DIRECTORY_RECORD, Integer.toString( action.getIdRecord( ) ) ) );
247 
248                     this.appendKey( nDirectoryId, action.getIdRecord( ), hm );
249                 }
250 
251                 DirectorySearchService.getInstance( ).removeIndexerAction( action.getIdAction( ), plugin );
252             }
253 
254             hm = this.indexListRecord( indexWriter, hm, plugin );
255 
256             // add all record which must be add
257             for ( IndexerAction action : DirectorySearchService.getInstance( ).getAllIndexerActionByTask( IndexerAction.TASK_CREATE, plugin ) )
258             {
259                 Integer nDirectoryId = recordService.getDirectoryIdByRecordId( Integer.valueOf( action.getIdRecord( ) ), plugin );
260 
261                 if ( nDirectoryId != null )
262                 {
263                     sbLogRecord( sbLogs, action.getIdRecord( ), nDirectoryId, IndexerAction.TASK_CREATE );
264 
265                     this.appendKey( nDirectoryId, action.getIdRecord( ), hm );
266                 }
267 
268                 DirectorySearchService.getInstance( ).removeIndexerAction( action.getIdAction( ), plugin );
269             }
270 
271             hm = this.indexListRecord( indexWriter, hm, plugin );
272         }
273         else
274         {
275             // Index only the directories that have the attribute is_indexed as true
276             DirectoryFilter filter = new DirectoryFilter( );
277             filter.setIsIndexed( DirectoryFilter.FILTER_TRUE );
278 
279             for ( Directory directory : DirectoryHome.getDirectoryList( filter, plugin ) )
280             {
281                 sbLogs.append( "Indexing Directory" );
282                 sbLogs.append( "\r\n" );
283                 recordFieldFilter.setIdDirectory( directory.getIdDirectory( ) );
284 
285                 for ( Record record : recordService.getListRecord( recordFieldFilter, plugin ) )
286                 {
287                     sbLogRecord( sbLogs, record.getIdRecord( ), record.getDirectory( ).getIdDirectory( ), IndexerAction.TASK_CREATE );
288 
289                     this.appendKey( directory.getIdDirectory( ), record.getIdRecord( ), hm );
290                 }
291             }
292 
293             hm = this.indexListRecord( indexWriter, hm, plugin );
294         }
295     }
296 
297     /**
298      * Builds a document which will be used by Lucene during the indexing of the directory
299      *
300      * @param recordField
301      *            the recordField to index
302      * @param record
303      *            the record associate to the recordField
304      * @param directory
305      *            the directory associate to the recordField
306      * @return A Lucene {@link Document} containing QuestionAnswer Data
307      * @throws IOException
308      *             The IO Exception
309      * @throws InterruptedException
310      *             The InterruptedException
311      */
312     private org.apache.lucene.document.Document getDocument( RecordField recordField, Record record, Directory directory ) throws IOException,
313             InterruptedException
314     {
315         // make a new, empty document
316         org.apache.lucene.document.Document doc = new org.apache.lucene.document.Document( );
317 
318         FieldType ft = new FieldType( StringField.TYPE_STORED );
319         ft.setOmitNorms( false );
320 
321         HashMap<String, Object> mapSearchItemField;
322         doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY, Integer.toString( directory.getIdDirectory( ) ), ft ) );
323 
324         doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY_RECORD, Integer.toString( record.getIdRecord( ) ), ft ) );
325 
326         doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY_ENTRY, Integer.toString( recordField.getEntry( ).getIdEntry( ) ), ft ) );
327 
328         mapSearchItemField = new HashMap<String, Object>( );
329         recordField.getEntry( ).addSearchCriteria( mapSearchItemField, recordField );
330 
331         if ( mapSearchItemField.containsKey( DirectorySearchItem.FIELD_ID_DIRECTORY_FIELD ) )
332         {
333             for ( Integer idField : (List<Integer>) mapSearchItemField.get( DirectorySearchItem.FIELD_ID_DIRECTORY_FIELD ) )
334             {
335                 doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY_FIELD, Integer.toString( idField ), ft ) );
336             }
337         }
338 
339         if ( mapSearchItemField.containsKey( DirectorySearchItem.FIELD_DATE ) )
340         {
341             String strDate = DateTools.dateToString( (Date) mapSearchItemField.get( DirectorySearchItem.FIELD_DATE ), DateTools.Resolution.DAY );
342             doc.add( new Field( DirectorySearchItem.FIELD_DATE, strDate, ft ) );
343         }
344 
345         if ( mapSearchItemField.containsKey( DirectorySearchItem.FIELD_CONTENTS ) )
346         {
347             doc.add( new Field( DirectorySearchItem.FIELD_CONTENTS, (String) mapSearchItemField.get( DirectorySearchItem.FIELD_CONTENTS ),
348                     TextField.TYPE_NOT_STORED ) );
349         }
350 
351         // return the document
352         return doc;
353     }
354 
355     /**
356      * Builds a document which will be used by Lucene during the indexing of the directory
357      *
358      * @param record
359      *            the record to index
360      * @param directory
361      *            the directory associate to the recordField
362      * @return A Lucene Document containing QuestionAnswer Data
363      * @throws IOException
364      *             The IO Exception
365      * @throws InterruptedException
366      *             The InterruptedException
367      */
368     private org.apache.lucene.document.Document getDocument( Record record, Directory directory ) throws IOException, InterruptedException
369     {
370         // make a new, empty document
371         org.apache.lucene.document.Document doc = new org.apache.lucene.document.Document( );
372 
373         FieldType ft = new FieldType( StringField.TYPE_STORED );
374         ft.setOmitNorms( false );
375 
376         if ( ( directory != null ) && ( record != null ) )
377         {
378             doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY, Integer.toString( directory.getIdDirectory( ) ), ft ) );
379 
380             doc.add( new Field( DirectorySearchItem.FIELD_ID_DIRECTORY_RECORD, Integer.toString( record.getIdRecord( ) ), ft ) );
381 
382             if ( record.getWorkgroup( ) != null )
383             {
384                 doc.add( new Field( DirectorySearchItem.FIELD_WORKGROUP_KEY, record.getWorkgroup( ), ft ) );
385             }
386 
387             if ( record.getRoleKey( ) != null )
388             {
389                 doc.add( new Field( DirectorySearchItem.FIELD_ROLE_KEY, record.getRoleKey( ), ft ) );
390             }
391 
392             String strDate = DateTools.dateToString( record.getDateCreation( ), DateTools.Resolution.DAY );
393             doc.add( new Field( DirectorySearchItem.FIELD_DATE_CREATION, strDate, ft ) );
394 
395             String strDateModification = DateTools.dateToString( record.getDateModification( ), DateTools.Resolution.DAY );
396             doc.add( new Field( DirectorySearchItem.FIELD_DATE_MODIFICATION, strDateModification, ft ) );
397         }
398 
399         // return the document
400         return doc;
401     }
402 
403     /**
404      * {@inheritDoc}
405      */
406     @Override
407     public String getName( )
408     {
409         return AppPropertiesService.getProperty( PROPERTY_INDEXER_NAME );
410     }
411 
412     /**
413      * {@inheritDoc}
414      */
415     @Override
416     public String getVersion( )
417     {
418         return AppPropertiesService.getProperty( PROPERTY_INDEXER_VERSION );
419     }
420 
421     /**
422      * {@inheritDoc}
423      */
424     @Override
425     public boolean isEnable( )
426     {
427         boolean bReturn = false;
428         String strEnable = AppPropertiesService.getProperty( PROPERTY_INDEXER_ENABLE );
429 
430         if ( ( strEnable != null ) && ( strEnable.equalsIgnoreCase( Boolean.TRUE.toString( ) ) || strEnable.equals( ENABLE_VALUE_TRUE ) ) )
431         {
432             bReturn = true;
433         }
434 
435         return bReturn;
436     }
437 
438     /**
439      * indexing action performed on the recording
440      * 
441      * @param sbLogs
442      *            the buffer log
443      * @param nIdRecord
444      *            the id of the record
445      * @param nIdDirectory
446      *            the id of the directory
447      * @param nAction
448      *            the indexer action key performed
449      */
450     private void sbLogRecord( StringBuffer sbLogs, int nIdRecord, int nIdDirectory, int nAction )
451     {
452         sbLogs.append( "Indexing Directory record:" );
453 
454         switch( nAction )
455         {
456             case IndexerAction.TASK_CREATE:
457                 sbLogs.append( "Insert " );
458 
459                 break;
460 
461             case IndexerAction.TASK_MODIFY:
462                 sbLogs.append( "Modify " );
463 
464                 break;
465 
466             case IndexerAction.TASK_DELETE:
467                 sbLogs.append( "Delete " );
468 
469                 break;
470 
471             default:
472                 break;
473         }
474 
475         sbLogs.append( "record :" );
476         sbLogs.append( "id_record=" );
477         sbLogs.append( nIdRecord );
478 
479         if ( nIdDirectory != DirectoryUtils.CONSTANT_ID_NULL )
480         {
481             sbLogs.append( "&" );
482             sbLogs.append( "id_directory=" );
483             sbLogs.append( nIdDirectory );
484         }
485 
486         sbLogs.append( "\r\n" );
487     }
488 
489     /**
490      * Append list record id to delete Hack (ugly) to bypass problem of primary key violation on table "directory_indexer_action" when inserting many records
491      * 
492      * @param lListIdRecord
493      *            List record to delete
494      */
495     public static void appendListRecordToDelete( List<Integer> lListIdRecord )
496     {
497         _lListIdRecordToDelete.addAll( lListIdRecord );
498     }
499 }