View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of 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.appointment.modules.management.service.search;
35  
36  import java.io.IOException;
37  import java.sql.Timestamp;
38  import java.time.LocalDate;
39  import java.time.LocalTime;
40  import java.util.List;
41  
42  import javax.inject.Inject;
43  
44  import org.apache.commons.lang3.StringUtils;
45  import org.apache.lucene.document.Document;
46  import org.apache.lucene.document.IntPoint;
47  import org.apache.lucene.document.LongPoint;
48  import org.apache.lucene.index.DirectoryReader;
49  import org.apache.lucene.index.IndexReader;
50  import org.apache.lucene.index.Term;
51  import org.apache.lucene.search.BooleanClause;
52  import org.apache.lucene.search.BooleanQuery;
53  import org.apache.lucene.search.IndexSearcher;
54  import org.apache.lucene.search.Query;
55  import org.apache.lucene.search.ScoreDoc;
56  import org.apache.lucene.search.Sort;
57  import org.apache.lucene.search.SortField;
58  import org.apache.lucene.search.SortedNumericSortField;
59  import org.apache.lucene.search.TermQuery;
60  import org.apache.lucene.search.TopDocs;
61  import org.apache.lucene.search.WildcardQuery;
62  import org.apache.lucene.store.Directory;
63  
64  import fr.paris.lutece.plugins.appointment.modules.management.business.search.AppointmentSearchItem;
65  import fr.paris.lutece.plugins.appointment.modules.management.business.search.MultiviewFilter;
66  import fr.paris.lutece.plugins.appointment.modules.management.service.indexer.LuceneAppointmentIndexFactory;
67  import fr.paris.lutece.portal.service.search.LuceneSearchEngine;
68  import fr.paris.lutece.portal.service.util.AppLogService;
69  
70  public class AppointmentSearchEngine implements IAppointmentSearchEngine
71  {
72  
73      @Inject
74      private LuceneAppointmentIndexFactory _indexFactory;
75  
76      @Override
77      public int getSearchResult( List<AppointmentSearchItem> result, MultiviewFilter filter, int nStartIndex, int nPageSize, AppointmentSortConfig sortConfig )
78      {
79          int nbResults = 0;
80          Query query = createQuery( filter );
81          Sort sort = buildLuceneSort( sortConfig );
82  
83          try ( Directory directory = _indexFactory.getDirectory( ) ; IndexReader ir = DirectoryReader.open( directory ) ; )
84          {
85              IndexSearcher searcher = new IndexSearcher( ir );
86              TopDocs topDocs = null;
87              // Get results documents
88              if ( sort != null )
89              {
90                  topDocs = searcher.search( query, LuceneSearchEngine.MAX_RESPONSES, sort );
91              }
92              else
93              {
94                  topDocs = searcher.search( query, LuceneSearchEngine.MAX_RESPONSES );
95              }
96  
97              ScoreDoc [ ] hits = topDocs.scoreDocs;
98              nbResults = hits.length;
99              int nMaxIndex = hits.length;
100             if ( nPageSize > 0 )
101             {
102                 nMaxIndex = Math.min( nStartIndex + nPageSize, hits.length );
103             }
104 
105             for ( int i = nStartIndex; i < nMaxIndex; i++ )
106             {
107                 Document document = searcher.doc( hits [i].doc );
108                 result.add( new AppointmentSearchItem( document ) );
109             }
110         }
111         catch( IOException e )
112         {
113             AppLogService.error( e.getMessage( ), e );
114         }
115         return nbResults;
116     }
117 
118     private Query createQuery( MultiviewFilter filter )
119     {
120         BooleanQuery.Builder builder = new BooleanQuery.Builder( );
121         if ( filter.getIdCategory( ) > 0 )
122         {
123             Query query = IntPoint.newExactQuery( AppointmentSearchItem.FIELD_ID_CATEGORY, filter.getIdCategory( ) );
124             builder.add( query, BooleanClause.Occur.MUST );
125         }
126         if ( filter.getIdForm( ) > 0 )
127         {
128             Query query = IntPoint.newExactQuery( AppointmentSearchItem.FIELD_ID_FORM, filter.getIdForm( ) );
129             builder.add( query, BooleanClause.Occur.MUST );
130         }
131         else
132         {
133             Query query = IntPoint.newSetQuery( AppointmentSearchItem.FIELD_ID_FORM, filter.getIdFormList( ) );
134             builder.add( query, BooleanClause.Occur.MUST );
135         }
136         if ( StringUtils.isNotEmpty( filter.getFirstName( ) ) )
137         {
138             Query query = new WildcardQuery( new Term( AppointmentSearchItem.FIELD_FIRST_NAME_SEARCH,
139                     WildcardQuery.WILDCARD_STRING + filter.getFirstName( ).toLowerCase( ) + WildcardQuery.WILDCARD_STRING ) );
140             builder.add( query, BooleanClause.Occur.MUST );
141         }
142         if ( StringUtils.isNotEmpty( filter.getLastName( ) ) )
143         {
144             Query query = new WildcardQuery( new Term( AppointmentSearchItem.FIELD_LAST_NAME_SEARCH,
145                     WildcardQuery.WILDCARD_STRING + filter.getLastName( ).toLowerCase( ) + WildcardQuery.WILDCARD_STRING ) );
146             builder.add( query, BooleanClause.Occur.MUST );
147         }
148         if ( StringUtils.isNotEmpty( filter.getEmail( ) ) )
149         {
150             Query query = new WildcardQuery( new Term( AppointmentSearchItem.FIELD_MAIL_SEARCH,
151                     WildcardQuery.WILDCARD_STRING + filter.getEmail( ).toLowerCase( ) + WildcardQuery.WILDCARD_STRING ) );
152             builder.add( query, BooleanClause.Occur.MUST );
153         }
154         if ( StringUtils.isNotEmpty( filter.getPhoneNumber( ) ) )
155         {
156             Query query = new WildcardQuery( new Term( AppointmentSearchItem.FIELD_PHONE_NUMBER,
157                     WildcardQuery.WILDCARD_STRING + filter.getPhoneNumber( ).toLowerCase( ) + WildcardQuery.WILDCARD_STRING ) );
158             builder.add( query, BooleanClause.Occur.MUST );
159         }
160         builder.add( createDateRangeQuery( filter ), BooleanClause.Occur.MUST );
161         if ( filter.getStatus( ) != -1 )
162         {
163             Query query = new TermQuery( new Term( AppointmentSearchItem.FIELD_CANCELLED, String.valueOf( filter.getStatus( ) == 1 ) ) );
164             builder.add( query, BooleanClause.Occur.MUST );
165         }
166 
167         return builder.build( );
168     }
169 
170     private Query createDateRangeQuery( MultiviewFilter filter )
171     {
172         Query query = null;
173         Timestamp startingTimestamp = null;
174         if ( filter.getStartingDateOfSearch( ) != null )
175         {
176             LocalDate startingDate = filter.getStartingDateOfSearch( ).toLocalDate( );
177             if ( StringUtils.isNotEmpty( filter.getStartingTimeOfSearch( ) ) )
178             {
179                 startingTimestamp = Timestamp.valueOf( startingDate.atTime( LocalTime.parse( filter.getStartingTimeOfSearch( ) ) ) );
180             }
181             else
182             {
183                 startingTimestamp = Timestamp.valueOf( startingDate.atStartOfDay( ) );
184             }
185         }
186         Timestamp endingTimestamp = null;
187         if ( filter.getEndingDateOfSearch( ) != null )
188         {
189             LocalDate startingDate = filter.getEndingDateOfSearch( ).toLocalDate( );
190             if ( StringUtils.isNotEmpty( filter.getEndingTimeOfSearch( ) ) )
191             {
192                 endingTimestamp = Timestamp.valueOf( startingDate.atTime( LocalTime.parse( filter.getEndingTimeOfSearch( ) ) ) );
193             }
194             else
195             {
196                 endingTimestamp = Timestamp.valueOf( startingDate.atTime( LocalTime.MAX ) );
197             }
198         }
199         if ( startingTimestamp != null && endingTimestamp != null )
200         {
201             query = LongPoint.newRangeQuery( AppointmentSearchItem.FIELD_START_DATE, startingTimestamp.getTime( ), endingTimestamp.getTime( ) );
202         }
203         else
204             if ( startingTimestamp != null )
205             {
206                 query = LongPoint.newRangeQuery( AppointmentSearchItem.FIELD_START_DATE, startingTimestamp.getTime( ), Long.MAX_VALUE );
207             }
208             else
209                 if ( endingTimestamp != null )
210                 {
211                     query = LongPoint.newRangeQuery( AppointmentSearchItem.FIELD_START_DATE, Long.MIN_VALUE, endingTimestamp.getTime( ) );
212                 }
213                 else
214                 {
215                     query = LongPoint.newRangeQuery( AppointmentSearchItem.FIELD_START_DATE, Long.MIN_VALUE, Long.MAX_VALUE );
216                 }
217         return query;
218     }
219 
220     /**
221      * Build the Lucene Sort obj
222      * 
223      * @param sortConfig
224      *            The sort config
225      * @return the Lucene Sort obj
226      */
227     private Sort buildLuceneSort( AppointmentSortConfig sortConfig )
228     {
229         if ( sortConfig != null )
230         {
231             String strAttributeName = sortConfig.getSortAttributeName( );
232             if ( strAttributeName != null )
233             {
234                 if ( strAttributeName.endsWith( AppointmentSearchItem.FIELD_DATE_SUFFIX ) )
235                 {
236                     return new Sort( new SortedNumericSortField( sortConfig.getSortAttributeName( ), SortField.Type.LONG, sortConfig.isDescSort( ) ) );
237                 }
238                 if ( strAttributeName.endsWith( AppointmentSearchItem.FIELD_INT_SUFFIX ) )
239                 {
240                     return new Sort( new SortedNumericSortField( sortConfig.getSortAttributeName( ), SortField.Type.LONG, sortConfig.isDescSort( ) ) );
241 
242                 }
243                 return new Sort( new SortField( sortConfig.getSortAttributeName( ), SortField.Type.STRING, sortConfig.isDescSort( ) ) );
244             }
245         }
246 
247         return null;
248     }
249 }