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.solr.service;
35  
36  import java.io.IOException;
37  import java.time.LocalDateTime;
38  import java.util.Comparator;
39  import java.util.List;
40  import java.util.Queue;
41  import java.util.concurrent.ConcurrentHashMap;
42  import java.util.concurrent.ConcurrentLinkedQueue;
43  import java.util.concurrent.ConcurrentMap;
44  import java.util.concurrent.atomic.AtomicBoolean;
45  
46  import javax.inject.Inject;
47  
48  import org.apache.solr.client.solrj.SolrServerException;
49  import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
50  import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
51  import fr.paris.lutece.plugins.appointment.business.slot.Slot;
52  import fr.paris.lutece.plugins.appointment.service.AppointmentExecutorService;
53  import fr.paris.lutece.plugins.appointment.service.FormService;
54  import fr.paris.lutece.plugins.appointment.service.ReservationRuleService;
55  import fr.paris.lutece.plugins.appointment.service.SlotService;
56  import fr.paris.lutece.plugins.appointment.service.listeners.IFormListener;
57  import fr.paris.lutece.plugins.appointment.service.listeners.ISlotListener;
58  import fr.paris.lutece.plugins.appointment.service.listeners.IWeekDefinitionListener;
59  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
60  import fr.paris.lutece.portal.service.util.AppLogService;
61  
62  /**
63   * Appointment listeners for Solr
64   * 
65   * @author Laurent Payen
66   *
67   */
68  public class SolrAppointmentListener implements IFormListener, ISlotListener, IWeekDefinitionListener
69  {
70      private static ConcurrentMap<Integer, AtomicBoolean> _lockIndexerIsRuning = new ConcurrentHashMap<>( );
71      private static ConcurrentMap<Integer, AtomicBoolean> _lockIndexToLunch = new ConcurrentHashMap<>( );
72      private static Queue<Slot> _queueSlotToIndex = new ConcurrentLinkedQueue<>( );
73      private static AtomicBoolean _bIndexIsRunning = new AtomicBoolean( false );
74      @Inject
75      private SolrAppointmentIndexer _solrAppointmentIndexer;
76  
77      /**
78       * Reindex the form and the slots in solr
79       * 
80       * @param nIdForm
81       *            the form id
82       */
83      private void reindexForm( final int nIdForm )
84      {
85          AtomicBoolean bIndexIsRunning = getIndexRuningLock( nIdForm );
86          AtomicBoolean bIndexToLunch = getIndexToLunchLock( nIdForm );
87          bIndexToLunch.set( true );
88          if ( bIndexIsRunning.compareAndSet( false, true ) )
89          {
90              AppointmentExecutorService.INSTANCE.execute( ( ) -> {
91  
92                  StringBuilder sbLogs = new StringBuilder( );
93                  try
94                  {
95                      sbLogs = new StringBuilder( );
96                      while ( bIndexToLunch.compareAndSet( true, false ) )
97                      {
98                          AppointmentFormDTO appointmentForm = FormService.buildAppointmentFormWithoutReservationRule( nIdForm );
99                          _solrAppointmentIndexer.deleteFormAndListSlots( nIdForm, sbLogs );
100                         if ( appointmentForm.getIsActive( ) )
101                         {
102                             _solrAppointmentIndexer.writeFormAndListSlots( appointmentForm, sbLogs );
103                         }
104                     }
105                 }
106                 catch( IOException | SolrServerException e )
107                 {
108                     AppLogService.error( "Error during SolrAppointmentListener reindexForm: " + sbLogs, e );
109                 }
110                 finally
111                 {
112                     bIndexIsRunning.set( false );
113                 }
114             } );
115         }
116     }
117 
118     /**
119      * Reindex the slot (and the related form to have the good number of available places) in solr
120      * 
121      * @param nIdSlot
122      *            the slot id
123      */
124     private void reindexSlot( Slot slot )
125     {
126         if ( _bIndexIsRunning.compareAndSet( false, true ) )
127         {
128 
129             AppointmentExecutorService.INSTANCE.execute( ( ) -> {
130 
131                 StringBuilder sbLogs = new StringBuilder( );
132                 try
133                 {
134                     _solrAppointmentIndexer.writeSlotAndForm( slot, sbLogs, _queueSlotToIndex );
135                 }
136                 catch( IOException e )
137                 {
138                     AppLogService.error( "Error during SolrAppointmentListener reindexSlot: " + sbLogs, e );
139                 }
140                 finally
141                 {
142                     _bIndexIsRunning.set( false );
143                     if ( !_queueSlotToIndex.isEmpty( ) )
144                     {
145                         reindexSlot( _queueSlotToIndex.poll( ) );
146                     }
147                 }
148             } );
149         }
150         else
151         {
152 
153             _queueSlotToIndex.add( slot );
154         }
155 
156     }
157 
158     /**
159      * Delete the form and all its slots in solr
160      * 
161      * @param nIdForm
162      *            the form id
163      */
164     private void deleteForm( int nIdForm )
165     {
166         StringBuilder sbLogs = new StringBuilder( );
167         try
168         {
169             _solrAppointmentIndexer.deleteFormAndListSlots( nIdForm, sbLogs );
170         }
171         catch( IOException | SolrServerException e )
172         {
173             AppLogService.error( "Error during SolrAppointmentListener deleteForm: " + sbLogs, e );
174         }
175     }
176 
177     private static synchronized AtomicBoolean getIndexRuningLock( int nkey )
178     {
179         _lockIndexerIsRuning.putIfAbsent( nkey, new AtomicBoolean( false ) );
180         return _lockIndexerIsRuning.get( nkey );
181     }
182 
183     private static synchronized AtomicBoolean getIndexToLunchLock( int nkey )
184     {
185         _lockIndexToLunch.putIfAbsent( nkey, new AtomicBoolean( false ) );
186         return _lockIndexToLunch.get( nkey );
187     }
188 
189     @Override
190     public void notifySlotChange( int nIdSlot )
191     {
192         Slot slot = SlotService.findSlotById( nIdSlot );
193         reindexSlot( slot );
194     }
195 
196     @Override
197     public void notifySlotCreation( int nIdSlot )
198     {
199         notifySlotChange( nIdSlot );
200     }
201 
202     @Override
203     public void notifySlotRemoval( Slot slot )
204     {
205         if ( FormUtil.isPeriodValidToIndex( slot.getIdForm( ), slot.getDate( ), slot.getDate( ) ) )
206         {
207             reindexForm( slot.getIdForm( ) );
208         }
209     }
210 
211     @Override
212     public void notifySlotEndingTimeHasChanged( int nIdSlot, int nIdFom, LocalDateTime endingDateTime )
213     {
214 
215         if ( FormUtil.isPeriodValidToIndex( nIdFom, endingDateTime.toLocalDate( ), endingDateTime.toLocalDate( ) ) )
216         {
217 
218             reindexForm( nIdFom );
219         }
220 
221     }
222 
223     @Override
224     public void notifyFormChange( int nIdForm )
225     {
226         reindexForm( nIdForm );
227     }
228 
229     @Override
230     public void notifyFormCreation( int nIdForm )
231     {
232         reindexForm( nIdForm );
233     }
234 
235     @Override
236     public void notifyFormRemoval( int nIdForm )
237     {
238         deleteForm( nIdForm );
239     }
240 
241     @Override
242     public void notifyWeekAssigned( WeekDefinition week )
243     {
244 
245         ReservationRule rule = ReservationRuleService.findReservationRuleById( week.getIdReservationRule( ) );
246         if ( FormUtil.isPeriodValidToIndex( rule.getIdForm( ), week.getDateOfApply( ), week.getEndingDateOfApply( ) ) )
247         {
248 
249             reindexForm( rule.getIdForm( ) );
250         }
251 
252     }
253 
254     @Override
255     public void notifyWeekUnassigned( WeekDefinition week )
256     {
257 
258         notifyWeekAssigned( week );
259     }
260 
261     @Override
262     public void notifyListWeeksChanged( int nIdForm, List<WeekDefinition> listWeek )
263     {
264 
265         WeekDefinition weekWithDateMin = listWeek.stream( ).min( Comparator.comparing( WeekDefinition::getDateOfApply ) ).orElse( null );
266         WeekDefinition weekWithDateMax = listWeek.stream( ).max( Comparator.comparing( WeekDefinition::getEndingDateOfApply ) ).orElse( null );
267         if ( weekWithDateMin != null && weekWithDateMax != null
268                 && FormUtil.isPeriodValidToIndex( nIdForm, weekWithDateMin.getDateOfApply( ), weekWithDateMax.getEndingDateOfApply( ) ) )
269         {
270 
271             reindexForm( nIdForm );
272         }
273     }
274 
275 }