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.helpdesk.modules.solr.search;
35  
36  import java.io.ByteArrayInputStream;
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.io.Reader;
40  import java.io.StringReader;
41  import java.nio.charset.StandardCharsets;
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.List;
45  
46  import org.apache.commons.lang.StringUtils;
47  import org.apache.tika.exception.TikaException;
48  import org.apache.tika.metadata.Metadata;
49  import org.apache.tika.parser.ParseContext;
50  import org.apache.tika.parser.html.HtmlParser;
51  import org.apache.tika.sax.BodyContentHandler;
52  import org.xml.sax.ContentHandler;
53  import org.xml.sax.SAXException;
54  
55  import fr.paris.lutece.plugins.helpdesk.business.Faq;
56  import fr.paris.lutece.plugins.helpdesk.business.FaqHome;
57  import fr.paris.lutece.plugins.helpdesk.business.QuestionAnswer;
58  import fr.paris.lutece.plugins.helpdesk.business.Subject;
59  import fr.paris.lutece.plugins.helpdesk.business.SubjectHome;
60  import fr.paris.lutece.plugins.helpdesk.service.HelpdeskPlugin;
61  import fr.paris.lutece.plugins.helpdesk.service.helpdesksearch.HelpdeskSearchItem;
62  import fr.paris.lutece.plugins.helpdesk.utils.HelpdeskIndexerUtils;
63  import fr.paris.lutece.plugins.helpdesk.web.HelpdeskApp;
64  import fr.paris.lutece.plugins.search.solr.business.field.Field;
65  import fr.paris.lutece.plugins.search.solr.indexer.SolrIndexer;
66  import fr.paris.lutece.plugins.search.solr.indexer.SolrIndexerService;
67  import fr.paris.lutece.plugins.search.solr.indexer.SolrItem;
68  import fr.paris.lutece.plugins.search.solr.util.SolrConstants;
69  import fr.paris.lutece.portal.service.content.XPageAppService;
70  import fr.paris.lutece.portal.service.plugin.Plugin;
71  import fr.paris.lutece.portal.service.plugin.PluginService;
72  import fr.paris.lutece.portal.service.util.AppLogService;
73  import fr.paris.lutece.portal.service.util.AppPropertiesService;
74  import fr.paris.lutece.util.url.UrlItem;
75  
76  
77  /**
78   * The Helpdesk indexer for Solr search platform
79   *
80   */
81  public class SolrHelpdeskIndexer implements SolrIndexer
82  {
83      private static final String PROPERTY_DESCRIPTION = "helpdesk-solr.indexer.description";
84      private static final String PROPERTY_NAME = "helpdesk-solr.indexer.name";
85      private static final String PROPERTY_VERSION = "helpdesk-solr.indexer.version";
86      private static final String PROPERTY_INDEXER_ENABLE = "helpdesk-solr.indexer.enable";
87      public static final String SHORT_NAME_SUBJECT = "hds";
88      public static final String SHORT_NAME_QUESTION_ANSWER = "hdq";
89      private static final String BLANK = " ";
90      private static final String PROPERTY_PAGE_PATH_LABEL = "helpdesk.pagePathLabel";
91      private static final String PROPERTY_FAQ_ID_LABEL = "helpdesk-solr.indexer.faq_id.label";
92      private static final String PROPERTY_FAQ_ID_DESCRIPTION = "helpdesk-solr.indexer.faq_id.description";
93      private static final String PROPERTY_SUBJECT_LABEL = "helpdesk-solr.indexer.subject.label";
94      private static final String PROPERTY_SUBJECT_DESCRIPTION = "helpdesk-solr.indexer.subject.description";
95  
96      // Site name
97      private static final List<String> LIST_RESSOURCES_NAME = new ArrayList<String>(  );
98      private static final String SUBJECT_INDEXATION_ERROR = "An error occured during the indexation of the subject number ";
99      
100     public SolrHelpdeskIndexer(  )
101     {
102         super(  );
103         LIST_RESSOURCES_NAME.add( HelpdeskIndexerUtils.CONSTANT_QUESTION_ANSWER_TYPE_RESOURCE );
104         LIST_RESSOURCES_NAME.add( HelpdeskIndexerUtils.CONSTANT_SUBJECT_TYPE_RESOURCE );
105     }
106 
107     /**
108      * {@inheritDoc}
109      */
110     public String getDescription(  )
111     {
112         return AppPropertiesService.getProperty( PROPERTY_DESCRIPTION );
113     }
114 
115     /**
116      * {@inheritDoc}
117      */
118     public String getName(  )
119     {
120         return AppPropertiesService.getProperty( PROPERTY_NAME );
121     }
122 
123     /**
124      * {@inheritDoc}
125      */
126     public String getVersion(  )
127     {
128         return AppPropertiesService.getProperty( PROPERTY_VERSION );
129     }
130 
131     /**
132      * {@inheritDoc}
133      */
134     public List<String> indexDocuments(  )
135     {
136         Plugin plugin = PluginService.getPlugin( HelpdeskPlugin.PLUGIN_NAME );
137         List<String> lstErrors = new ArrayList<String>(  );
138         
139         //FAQ
140         for ( Faq faq : FaqHome.findAll( plugin ) )
141         {
142         	for ( Subject subject : (Collection<Subject>) SubjectHome.getInstance(  ).findByIdFaq( faq.getId(  ), plugin ) )
143         	{
144         		try
145         		{
146         			indexSubject( faq, subject );
147         		}
148         		catch ( IOException e )
149         		{
150         			lstErrors.add( SolrIndexerService.buildErrorMessage( e ) );
151         			AppLogService.error( SUBJECT_INDEXATION_ERROR + subject.getId(  ), e );
152         		}
153         	}
154         }
155         
156         return lstErrors;
157     }
158 
159     /**
160      * Get the subject document
161      * @param strDocument id of the subject to index
162      * @return The list of Solr items
163      */
164     public List<SolrItem> getDocuments( String strDocument )
165     {
166         List<SolrItem> listDocs = new ArrayList<SolrItem>(  );
167         String strPortalUrl = SolrIndexerService.getBaseUrl(  );
168         Plugin plugin = PluginService.getPlugin( HelpdeskPlugin.PLUGIN_NAME );
169 
170         Subject subject = (Subject) SubjectHome.getInstance(  ).findByPrimaryKey( Integer.parseInt( strDocument ),
171                 plugin );
172 
173         if ( subject != null )
174         {
175             UrlItem urlSubject = new UrlItem( strPortalUrl );
176             urlSubject.addParameter( XPageAppService.PARAM_XPAGE_APP,
177                 AppPropertiesService.getProperty( PROPERTY_PAGE_PATH_LABEL ) ); //FIXME
178 
179             //if it's a sub-subject, we need to get the first parent to have the faq
180             int nIdParent = subject.getIdParent(  );
181             Subject parentSubject = subject;
182 
183             while ( nIdParent != SubjectHome.FIRST_ORDER )
184             {
185                 parentSubject = (Subject) SubjectHome.getInstance(  ).findByPrimaryKey( nIdParent, plugin );
186                 nIdParent = parentSubject.getIdParent(  );
187             }
188 
189             Faq faq = FaqHome.findBySubjectId( parentSubject.getId(  ), plugin );
190 
191             if ( faq != null )
192             {
193                 urlSubject.addParameter( HelpdeskApp.PARAMETER_FAQ_ID, faq.getId(  ) );
194                 urlSubject.setAnchor( HelpdeskApp.ANCHOR_SUBJECT + subject.getId(  ) );
195 
196                 SolrItem docSubject;
197 
198                 try
199                 {
200                     docSubject = getDocument( subject, faq.getRoleKey(  ), urlSubject.getUrl(  ), plugin );
201 
202                     listDocs.add( docSubject );
203 
204                     for ( QuestionAnswer questionAnswer : (List<QuestionAnswer>) subject.getQuestions(  ) )
205                     {
206                         if ( questionAnswer.isEnabled(  ) )
207                         {
208                             UrlItem urlQuestionAnswer = new UrlItem( strPortalUrl );
209                             urlQuestionAnswer.addParameter( XPageAppService.PARAM_XPAGE_APP,
210                                 AppPropertiesService.getProperty( PROPERTY_PAGE_PATH_LABEL ) ); //FIXME
211                             urlQuestionAnswer.addParameter( HelpdeskApp.PARAMETER_FAQ_ID, faq.getId(  ) );
212                             urlQuestionAnswer.setAnchor( HelpdeskApp.ANCHOR_QUESTION_ANSWER +
213                                 questionAnswer.getIdQuestionAnswer(  ) );
214 
215                             SolrItem docQuestionAnswer = getDocument( faq.getId(  ), questionAnswer,
216                                     urlQuestionAnswer.getUrl(  ), faq.getRoleKey(  ), plugin );
217                             listDocs.add( docQuestionAnswer );
218                         }
219                     }
220                 }
221                 catch ( IOException e )
222                 {
223                     throw new RuntimeException( e );
224                 }
225             }
226         }
227 
228         return listDocs;
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     public boolean isEnable(  )
235     {
236         return "true".equalsIgnoreCase( AppPropertiesService.getProperty( PROPERTY_INDEXER_ENABLE ) );
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     public List<Field> getAdditionalFields(  )
243     {
244         List<Field> fields = new ArrayList<Field>(  );
245         Field fieldFaqId = new Field(  );
246         fieldFaqId.setEnableFacet( false );
247         fieldFaqId.setName( HelpdeskSearchItem.FIELD_FAQ_ID );
248         fieldFaqId.setLabel( AppPropertiesService.getProperty( PROPERTY_FAQ_ID_LABEL ) );
249         fieldFaqId.setDescription( AppPropertiesService.getProperty( PROPERTY_FAQ_ID_DESCRIPTION ) );
250         fields.add( fieldFaqId );
251 
252         Field fieldSubjectId = new Field(  );
253         fieldSubjectId.setEnableFacet( true );
254         fieldSubjectId.setName( HelpdeskSearchItem.FIELD_SUBJECT );
255         fieldSubjectId.setLabel( AppPropertiesService.getProperty( PROPERTY_SUBJECT_LABEL ) );
256         fieldSubjectId.setDescription( AppPropertiesService.getProperty( PROPERTY_SUBJECT_DESCRIPTION ) );
257         fields.add( fieldSubjectId );
258 
259         return fields;
260     }
261 
262     /**
263      * Recursive method for indexing a subject and his children
264      *
265      * @param faq the faq linked to the subject
266      * @param subject the subject
267      * @throws IOException I/O Exception
268      * @throws InterruptedException interruptedException
269      */
270     private void indexSubject( Faq faq, Subject subject )
271         throws IOException
272     {
273         Plugin plugin = PluginService.getPlugin( HelpdeskPlugin.PLUGIN_NAME );
274 
275         String strWebappName = SolrIndexerService.getBaseUrl(  );
276         UrlItem urlSubject = new UrlItem( strWebappName );
277         urlSubject.addParameter( XPageAppService.PARAM_XPAGE_APP,
278             AppPropertiesService.getProperty( PROPERTY_PAGE_PATH_LABEL ) ); //FIXME
279         urlSubject.addParameter( HelpdeskApp.PARAMETER_FAQ_ID, faq.getId(  ) );
280         urlSubject.setAnchor( HelpdeskApp.ANCHOR_SUBJECT + subject.getId(  ) );
281 
282         // Indexing the subject
283         SolrItem itemSubject = getDocument( subject, faq.getRoleKey(  ), urlSubject.getUrl(  ), plugin );
284         SolrIndexerService.write( itemSubject );
285 
286         for ( QuestionAnswer questionAnswer : (List<QuestionAnswer>) subject.getQuestions(  ) )
287         {
288             if ( questionAnswer.isEnabled(  ) )
289             {
290                 UrlItem urlQuestionAnswer = new UrlItem( strWebappName );
291                 urlQuestionAnswer.addParameter( XPageAppService.PARAM_XPAGE_APP,
292                     AppPropertiesService.getProperty( PROPERTY_PAGE_PATH_LABEL ) ); //FIXME
293                 urlQuestionAnswer.addParameter( HelpdeskApp.PARAMETER_FAQ_ID, faq.getId(  ) );
294                 urlQuestionAnswer.setAnchor( HelpdeskApp.ANCHOR_QUESTION_ANSWER +
295                     questionAnswer.getIdQuestionAnswer(  ) );
296 
297                 // Indexing the questionAnswer
298                 SolrItem itemQuestionAnswer = getDocument( faq.getId(  ), questionAnswer, urlQuestionAnswer.getUrl(  ),
299                         faq.getRoleKey(  ), plugin );
300                 SolrIndexerService.write( itemQuestionAnswer );
301             }
302         }
303 
304         Collection<Subject> children = subject.getChilds( plugin );
305 
306         for ( Subject childSubject : children )
307         {
308             indexSubject( faq, childSubject );
309         }
310     }
311 
312     /**
313      * Builds a {@link SolrItem} which will be used by Solr during the indexing of the question/answer list
314      *
315      * @param nIdFaq The {@link Faq} Id
316      * @param questionAnswer the {@link QuestionAnswer} to index
317      * @param strUrl the url of the subject
318      * @param strRoleKey The role key
319      * @param plugin The {@link Plugin}
320      * @return A Solr {@link SolrItem} containing QuestionAnswer Data
321      * @throws IOException The IO Exception
322      */
323     private SolrItem getDocument( int nIdFaq, QuestionAnswer questionAnswer, String strUrl, String strRoleKey,
324         Plugin plugin ) throws IOException
325     {
326         // make a new, empty document
327         SolrItem item = new SolrItem(  );
328 
329         // Setting the Id faq field
330         item.addDynamicField( HelpdeskSearchItem.FIELD_FAQ_ID, String.valueOf( nIdFaq ) );
331 
332         // Setting the Role field
333         item.setRole( strRoleKey );
334 
335         // Setting the URL field
336         item.setUrl( strUrl );
337 
338         // Setting the subject field
339         item.addDynamicField( HelpdeskSearchItem.FIELD_SUBJECT, String.valueOf( questionAnswer.getIdSubject(  ) ) );
340 
341         // Setting the Uid field
342         String strIdQuestionAnswer = String.valueOf( questionAnswer.getIdQuestionAnswer(  ) );
343         item.setUid( getResourceUid( strIdQuestionAnswer, HelpdeskIndexerUtils.CONSTANT_QUESTION_ANSWER_TYPE_RESOURCE ) );
344 
345         // Setting the Date field
346         // Add the last modified date of the file a field named "modified".
347         item.setDate( questionAnswer.getCreationDate(  ) );
348 
349         //Setting the Content field
350         String strContentToIndex = getContentToIndex( questionAnswer, plugin );
351 
352         HtmlParser parser = new HtmlParser(  );
353         ContentHandler handler = new BodyContentHandler();
354         Metadata metadata = new Metadata();
355         InputStream stream = new ByteArrayInputStream(strContentToIndex.getBytes(StandardCharsets.UTF_8));
356         try {
357 			parser.parse(stream,  handler, metadata, new ParseContext());
358 		} catch (SAXException e) {
359 			e.printStackTrace();
360 		} catch (TikaException e) {
361 			e.printStackTrace();
362 		}
363         
364         item.setContent( handler.toString(  ) );
365 
366         // Setting the Title field
367         item.setTitle( questionAnswer.getQuestion(  ) );
368 
369         // Setting the Site field
370         item.setSite( SolrIndexerService.getWebAppName(  ) );
371 
372         // Setting the Type field
373         item.setType( HelpdeskPlugin.PLUGIN_NAME );
374 
375         // return the document
376         return item;
377     }
378 
379     /**
380      * Builds a {@link SolrItem} element which will be used by Solr during the indexing of the subject list
381      *
382      * @param subject the {@link Subject} to index
383      * @param strUrl the url of the subject
384      * @param strRoleKey The role key
385      * @param plugin The {@link Plugin}
386      * @return The Solr {@link SolrItem} containing Subject data
387      * @throws IOException The IO Exception
388      */
389     private SolrItem getDocument( Subject subject, String strRoleKey, String strUrl, Plugin plugin )
390         throws IOException
391     {
392         // make a new, empty document
393         SolrItem item = new SolrItem(  );
394 
395         // Setting the URL field
396         item.setUrl( strUrl );
397 
398         // Setting the Uid field
399         String strIdSubject = String.valueOf( subject.getId(  ) );
400         item.setUid( getResourceUid( strIdSubject, HelpdeskIndexerUtils.CONSTANT_SUBJECT_TYPE_RESOURCE ) );
401 
402         //Setting the Content field
403         String strContentToIndex = subject.getText(  );
404         
405         HtmlParser parser = new HtmlParser(  );
406         ContentHandler handler = new BodyContentHandler();
407         Metadata metadata = new Metadata();
408         InputStream stream = new ByteArrayInputStream(strContentToIndex.getBytes(StandardCharsets.UTF_8));
409         try {
410 			parser.parse(stream,  handler, metadata, new ParseContext());
411 		} catch (SAXException e) {
412 			e.printStackTrace();
413 		} catch (TikaException e) {
414 			e.printStackTrace();
415 		}
416         
417         item.setContent( strContentToIndex.toString(  ) );
418 
419         // Setting the Title field
420         item.setTitle( subject.getText(  ) );
421 
422         // Setting the Site field
423         item.setSite( SolrIndexerService.getWebAppName(  ) );
424 
425         // Setting the Type field
426         item.setType( HelpdeskPlugin.PLUGIN_NAME );
427 
428         // Setting the Role field
429         item.setRole( strRoleKey );
430 
431         // return the document
432         return item;
433     }
434 
435     /**
436      * Set the Content to index (Question and Answer)
437      * @param questionAnswer The {@link QuestionAnswer} to index
438      * @param plugin The {@link Plugin}
439      * @return The content to index
440      */
441     private static String getContentToIndex( QuestionAnswer questionAnswer, Plugin plugin )
442     {
443         StringBuffer sbContentToIndex = new StringBuffer(  );
444         //Do not index question here
445         sbContentToIndex.append( questionAnswer.getQuestion(  ) );
446         sbContentToIndex.append( BLANK );
447         sbContentToIndex.append( questionAnswer.getAnswer(  ) );
448 
449         return sbContentToIndex.toString(  );
450     }
451 
452     /**
453          * {@inheritDoc}
454          */
455     public List<String> getResourcesName(  )
456     {
457         return LIST_RESSOURCES_NAME;
458     }
459 
460     /**
461      * {@inheritDoc}
462      */
463     public String getResourceUid( String strResourceId, String strResourceType )
464     {
465         StringBuffer sb = new StringBuffer(  );
466 
467         if ( HelpdeskIndexerUtils.CONSTANT_QUESTION_ANSWER_TYPE_RESOURCE.equals( strResourceType ) )
468         {
469             sb.append( strResourceId ).append( SolrConstants.CONSTANT_UNDERSCORE ).append( SHORT_NAME_QUESTION_ANSWER );
470         }
471         else if ( HelpdeskIndexerUtils.CONSTANT_SUBJECT_TYPE_RESOURCE.equals( strResourceType ) )
472         {
473             sb.append( strResourceId ).append( SolrConstants.CONSTANT_UNDERSCORE ).append( SHORT_NAME_SUBJECT );
474         }
475 
476         return StringUtils.isNotBlank( sb.toString(  ) ) ? sb.toString(  ) : null;
477     }
478 }