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.modules.solr.search;
35  
36  import fr.paris.lutece.plugins.calendar.business.Agenda;
37  import fr.paris.lutece.plugins.calendar.business.CalendarHome;
38  import fr.paris.lutece.plugins.calendar.business.Event;
39  import fr.paris.lutece.plugins.calendar.business.OccurrenceEvent;
40  import fr.paris.lutece.plugins.calendar.business.SimpleEvent;
41  import fr.paris.lutece.plugins.calendar.business.category.Category;
42  import fr.paris.lutece.plugins.calendar.service.AgendaResource;
43  import fr.paris.lutece.plugins.calendar.service.CalendarPlugin;
44  import fr.paris.lutece.plugins.calendar.service.Utils;
45  import fr.paris.lutece.plugins.calendar.utils.CalendarIndexerUtils;
46  import fr.paris.lutece.plugins.calendar.web.Constants;
47  import fr.paris.lutece.plugins.search.solr.business.field.Field;
48  import fr.paris.lutece.plugins.search.solr.indexer.SolrIndexer;
49  import fr.paris.lutece.plugins.search.solr.indexer.SolrIndexerService;
50  import fr.paris.lutece.plugins.search.solr.indexer.SolrItem;
51  import fr.paris.lutece.plugins.search.solr.util.SolrConstants;
52  import fr.paris.lutece.portal.service.content.XPageAppService;
53  import fr.paris.lutece.portal.service.plugin.Plugin;
54  import fr.paris.lutece.portal.service.plugin.PluginService;
55  import fr.paris.lutece.portal.service.util.AppException;
56  import fr.paris.lutece.portal.service.util.AppLogService;
57  import fr.paris.lutece.portal.service.util.AppPropertiesService;
58  import fr.paris.lutece.util.url.UrlItem;
59  
60  import org.apache.tika.exception.TikaException;
61  import org.apache.tika.metadata.Metadata;
62  import org.apache.tika.parser.ParseContext;
63  import org.apache.tika.parser.html.HtmlParser;
64  import org.apache.tika.sax.BodyContentHandler;
65  
66  import org.xml.sax.ContentHandler;
67  import org.xml.sax.SAXException;
68  
69  import java.io.ByteArrayInputStream;
70  import java.io.IOException;
71  
72  import java.util.ArrayList;
73  import java.util.Collection;
74  import java.util.Iterator;
75  import java.util.List;
76  
77  
78  /**
79   * The Calendar indexer for Solr search platform
80   *
81   */
82  public class SolrCalendarIndexer implements SolrIndexer
83  {
84      private static final String PROPERTY_DESCRIPTION = "calendar-solr.indexer.description";
85      private static final String PROPERTY_NAME = "calendar-solr.indexer.name";
86      private static final String PROPERTY_VERSION = "calendar-solr.indexer.version";
87      private static final String PROPERTY_INDEXER_ENABLE = "calendar-solr.indexer.enable";
88      private static final String BLANK = " ";
89      private static final String PROPERTY_DESCRIPTION_MAX_CHARACTERS = "calendar-solr.description.max.characters";
90      private static final String PROPERTY_DESCRIPTION_ETC = "...";
91      private static final String PROPERTY_CALENDAR_ID_LABEL = "calendar-solr.indexer.calendar_id.label";
92      private static final String PROPERTY_CALENDAR_ID_DESCRIPTION = "calendar-solr.indexer.calendar_id.description";
93      private static final List<String> LIST_RESSOURCES_NAME = new ArrayList<String>(  );
94      private static final String EVENT_INDEXATION_ERROR = "[SolrCalendarIndexer] An error occured during the indexation of the event number ";
95  
96      public SolrCalendarIndexer(  )
97      {
98          super(  );
99  
100         LIST_RESSOURCES_NAME.add( CalendarIndexerUtils.CONSTANT_TYPE_RESOURCE );
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
107     public String getDescription(  )
108     {
109         return AppPropertiesService.getProperty( PROPERTY_DESCRIPTION );
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public String getName(  )
117     {
118         return AppPropertiesService.getProperty( PROPERTY_NAME );
119     }
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     public String getVersion(  )
126     {
127         return AppPropertiesService.getProperty( PROPERTY_VERSION );
128     }
129 
130     /**
131      * {@inheritDoc}
132      */
133     @Override
134     public List<String> indexDocuments(  )
135     {
136         String sRoleKey = "";
137 
138         List<String> lstErrors = new ArrayList<String>(  );
139 
140         for ( AgendaResource agenda : Utils.getAgendaResourcesWithOccurrences(  ) )
141         {
142             sRoleKey = agenda.getRole(  );
143 
144             String strAgenda = agenda.getId(  );
145 
146             for ( Object oEvent : agenda.getAgenda(  ).getEvents(  ) )
147             {
148                 try
149                 {
150                     indexSubject( oEvent, sRoleKey, strAgenda );
151                 }
152                 catch ( Exception e )
153                 {
154                     lstErrors.add( SolrIndexerService.buildErrorMessage( e ) );
155                     AppLogService.error( EVENT_INDEXATION_ERROR + ( (Event) oEvent ).getId(  ), e );
156                 }
157             }
158         }
159 
160         return lstErrors;
161     }
162 
163     /**
164      * Get the calendar document
165      * @param strDocument id of the subject to index
166      * @return The list of Solr items
167      */
168     @Override
169     public List<SolrItem> getDocuments( String strDocument )
170     {
171         List<SolrItem> listDocs = new ArrayList<SolrItem>(  );
172         Plugin plugin = PluginService.getPlugin( CalendarPlugin.PLUGIN_NAME );
173 
174         OccurrenceEvent occurrence = CalendarHome.findOccurrence( Integer.parseInt( strDocument ), plugin );
175 
176         if ( !occurrence.getStatus(  )
177                             .equals( AppPropertiesService.getProperty( Constants.PROPERTY_EVENT_STATUS_CONFIRMED ) ) )
178         {
179             return null;
180         }
181 
182         SimpleEvent event = CalendarHome.findEvent( occurrence.getEventId(  ), plugin );
183 
184         AgendaResource agendaResource = CalendarHome.findAgendaResource( event.getIdCalendar(  ), plugin );
185         Utils.loadAgendaOccurrences( agendaResource, plugin );
186 
187         String sRoleKey = agendaResource.getRole(  );
188         Agenda agenda = agendaResource.getAgenda(  );
189 
190         UrlItem urlEvent = new UrlItem( SolrIndexerService.getBaseUrl(  ) );
191         urlEvent.addParameter( XPageAppService.PARAM_XPAGE_APP, CalendarPlugin.PLUGIN_NAME );
192         urlEvent.addParameter( Constants.PARAMETER_ACTION, Constants.ACTION_SHOW_RESULT );
193         urlEvent.addParameter( Constants.PARAMETER_EVENT_ID, occurrence.getEventId(  ) );
194         urlEvent.addParameter( Constants.PARAM_AGENDA, agenda.getKeyName(  ) );
195 
196         SolrItem docEvent;
197 
198         try
199         {
200             docEvent = getDocument( occurrence, sRoleKey, urlEvent.getUrl(  ), agenda.getKeyName(  ) );
201             listDocs.add( docEvent );
202         }
203         catch ( IOException e )
204         {
205             throw new RuntimeException( e );
206         }
207 
208         return listDocs;
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     @Override
215     public boolean isEnable(  )
216     {
217         return "true".equalsIgnoreCase( AppPropertiesService.getProperty( PROPERTY_INDEXER_ENABLE ) );
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     public List<Field> getAdditionalFields(  )
225     {
226         List<Field> fields = new ArrayList<Field>(  );
227         Field field = new Field(  );
228         field.setEnableFacet( false );
229         field.setName( Constants.FIELD_CALENDAR_ID );
230         field.setLabel( AppPropertiesService.getProperty( PROPERTY_CALENDAR_ID_LABEL ) );
231         field.setDescription( AppPropertiesService.getProperty( PROPERTY_CALENDAR_ID_DESCRIPTION ) );
232         fields.add( field );
233 
234         return fields;
235     }
236 
237     /**
238      * Recursive method for indexing a calendar event
239      *
240      * @param faq the faq linked to the subject
241      * @param subject the subject
242      * @throws IOException I/O Exception
243      */
244     private void indexSubject( Object oEvent, String sRoleKey, String strAgenda )
245         throws IOException
246     {
247         OccurrenceEvent occurrence = (OccurrenceEvent) oEvent;
248 
249         if ( occurrence.getStatus(  )
250                            .equals( AppPropertiesService.getProperty( Constants.PROPERTY_EVENT_STATUS_CONFIRMED ) ) )
251         {
252             UrlItem urlEvent = new UrlItem( SolrIndexerService.getBaseUrl(  ) );
253             urlEvent.addParameter( XPageAppService.PARAM_XPAGE_APP, CalendarPlugin.PLUGIN_NAME );
254             urlEvent.addParameter( Constants.PARAMETER_ACTION, Constants.ACTION_SHOW_RESULT );
255             urlEvent.addParameter( Constants.PARAMETER_EVENT_ID, occurrence.getEventId(  ) );
256             urlEvent.addParameter( Constants.PARAM_AGENDA, strAgenda );
257 
258             SolrItem docSubject = getDocument( occurrence, sRoleKey, urlEvent.getUrl(  ), strAgenda );
259 
260             SolrIndexerService.write( docSubject );
261         }
262     }
263 
264     /**
265      * Builds a {@link SolrItem} which will be used by solr during the indexing of the calendar list
266      *
267      * @param nIdFaq The {@link Faq} Id
268      * @param strUrl the url of the subject
269      * @param strRoleKey The role key
270      * @param plugin The {@link Plugin}
271      * @param strAgenda the calendar id
272      * @return A Solr item containing QuestionAnswer Data
273      * @throws IOException The IO Exception
274      */
275     private SolrItem getDocument( OccurrenceEvent occurrence, String strRoleKey, String strUrl, String strAgenda )
276         throws IOException
277     {
278         // make a new, empty document
279         SolrItem item = new SolrItem(  );
280 
281         //add the id of the calendar
282         item.addDynamicField( Constants.FIELD_CALENDAR_ID, strAgenda + "_" + Constants.CALENDAR_SHORT_NAME );
283 
284         //add the category of the event
285         Collection<Category> arrayCategories = occurrence.getListCategories(  );
286         List<String> listCategories = new ArrayList<String>(  );
287 
288         if ( arrayCategories != null )
289         {
290             Iterator<Category> i = arrayCategories.iterator(  );
291 
292             while ( i.hasNext(  ) )
293                 listCategories.add( i.next(  ).getName(  ) );
294         }
295 
296         // Setting the Categorie field
297         item.setCategorie( listCategories );
298 
299         // Setting the Role field
300         item.setRole( strRoleKey );
301 
302         // Setting the Url field
303         item.setUrl( strUrl );
304 
305         // Setting the Uid field
306         String strIdEvent = String.valueOf( occurrence.getId(  ) );
307         item.setUid( getResourceUid( strIdEvent, CalendarIndexerUtils.CONSTANT_TYPE_RESOURCE ) );
308 
309         // Setting the date field
310         item.setDate( occurrence.getDate(  ) );
311 
312         // Setting the content field
313         String strContentToIndex = getContentToIndex( occurrence );
314         ContentHandler handler = new BodyContentHandler(  );
315         Metadata metadata = new Metadata(  );
316 
317         try
318         {
319             new HtmlParser(  ).parse( new ByteArrayInputStream( strContentToIndex.getBytes(  ) ), handler, metadata,
320                 new ParseContext(  ) );
321         }
322         catch ( SAXException e )
323         {
324             throw new AppException( "Error during page parsing." );
325         }
326         catch ( TikaException e )
327         {
328             throw new AppException( "Error during page parsing." );
329         }
330 
331         item.setContent( handler.toString(  ) );
332 
333         // Setting the summary field
334         // Add the description as a summary field, so that index can be incrementally maintained.
335         // This field is stored, but it is not indexed
336         String strDescription = occurrence.getDescription(  );
337         strDescription = Utils.parseHtmlToPlainTextString( strDescription );
338 
339         try
340         {
341             strDescription = strDescription.substring( 0,
342                     AppPropertiesService.getPropertyInt( PROPERTY_DESCRIPTION_MAX_CHARACTERS, 200 ) ) +
343                 PROPERTY_DESCRIPTION_ETC;
344         }
345         catch ( StringIndexOutOfBoundsException e )
346         {
347         }
348         catch ( NullPointerException e )
349         {
350         }
351 
352         item.setSummary( strDescription );
353 
354         // Setting the title field
355         item.setTitle( occurrence.getTitle(  ) );
356 
357         // Setting the Site field
358         item.setSite( SolrIndexerService.getWebAppName(  ) );
359 
360         // Setting the type field
361         item.setType( CalendarPlugin.PLUGIN_NAME );
362 
363         // return the item
364         return item;
365     }
366 
367     /**
368      * Set the Content to index (Description, location)
369      * @param  The Event
370      * @return The content to index
371      */
372     private String getContentToIndex( Event event )
373     {
374         StringBuffer sbContentToIndex = new StringBuffer(  );
375         //Do not index question here
376         sbContentToIndex.append( event.getDescription(  ) );
377         sbContentToIndex.append( BLANK );
378         sbContentToIndex.append( event.getLocationAddress(  ) );
379         sbContentToIndex.append( BLANK );
380         sbContentToIndex.append( event.getLocationTown(  ) );
381         sbContentToIndex.append( BLANK );
382         sbContentToIndex.append( event.getLocationZip(  ) );
383 
384         return sbContentToIndex.toString(  );
385     }
386 
387     /**
388          * {@inheritDoc}
389          */
390     public List<String> getResourcesName(  )
391     {
392         return LIST_RESSOURCES_NAME;
393     }
394 
395     /**
396      * {@inheritDoc}
397      */
398     public String getResourceUid( String strResourceId, String strResourceType )
399     {
400         StringBuffer sb = new StringBuffer( strResourceId );
401         sb.append( SolrConstants.CONSTANT_UNDERSCORE ).append( Constants.CALENDAR_SHORT_NAME );
402 
403         return sb.toString(  );
404     }
405 }