View Javadoc
1   /*
2    * Copyright (c) 2002-2021, 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.genericalert.service;
35  
36  import java.sql.Timestamp;
37  import java.time.format.DateTimeFormatter;
38  import java.util.ArrayList;
39  import java.util.Calendar;
40  import java.util.Date;
41  import java.util.GregorianCalendar;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.concurrent.TimeUnit;
45  
46  import javax.inject.Inject;
47  import javax.inject.Named;
48  import javax.servlet.http.HttpServletRequest;
49  
50  import org.apache.commons.lang3.StringUtils;
51  
52  import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
53  import fr.paris.lutece.plugins.appointment.business.localization.Localization;
54  import fr.paris.lutece.plugins.appointment.business.user.User;
55  import fr.paris.lutece.plugins.appointment.service.AppointmentResponseService;
56  import fr.paris.lutece.plugins.appointment.service.AppointmentService;
57  import fr.paris.lutece.plugins.appointment.service.FormService;
58  import fr.paris.lutece.plugins.appointment.service.LocalizationService;
59  import fr.paris.lutece.plugins.appointment.service.UserService;
60  import fr.paris.lutece.plugins.appointment.service.entrytype.EntryTypePhone;
61  import fr.paris.lutece.plugins.appointment.web.AppointmentApp;
62  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
63  import fr.paris.lutece.plugins.genericalert.business.ReminderAppointment;
64  import fr.paris.lutece.plugins.genericalert.business.TaskNotifyReminderConfig;
65  import fr.paris.lutece.plugins.genericalert.business.TaskNotifyReminderConfigHome;
66  import fr.paris.lutece.plugins.genericattributes.business.Entry;
67  import fr.paris.lutece.plugins.genericattributes.business.EntryFilter;
68  import fr.paris.lutece.plugins.genericattributes.business.EntryHome;
69  import fr.paris.lutece.plugins.genericattributes.business.Response;
70  import fr.paris.lutece.plugins.genericattributes.business.ResponseHome;
71  import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
72  import fr.paris.lutece.plugins.genericattributes.service.entrytype.IEntryTypeService;
73  import fr.paris.lutece.plugins.workflow.utils.WorkflowUtils;
74  import fr.paris.lutece.plugins.workflowcore.business.action.Action;
75  import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceHistory;
76  import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceWorkflow;
77  import fr.paris.lutece.plugins.workflowcore.business.state.State;
78  import fr.paris.lutece.plugins.workflowcore.service.action.IActionService;
79  import fr.paris.lutece.plugins.workflowcore.service.config.ITaskConfigService;
80  import fr.paris.lutece.plugins.workflowcore.service.resource.IResourceHistoryService;
81  import fr.paris.lutece.plugins.workflowcore.service.resource.IResourceWorkflowService;
82  import fr.paris.lutece.plugins.workflowcore.service.state.StateService;
83  import fr.paris.lutece.plugins.workflowcore.service.task.ITask;
84  import fr.paris.lutece.plugins.workflowcore.service.task.ITaskService;
85  import fr.paris.lutece.plugins.workflowcore.service.task.SimpleTask;
86  import fr.paris.lutece.plugins.workflowcore.service.workflow.IWorkflowService;
87  import fr.paris.lutece.portal.service.i18n.I18nService;
88  import fr.paris.lutece.portal.service.mail.MailService;
89  import fr.paris.lutece.portal.service.spring.SpringContextService;
90  import fr.paris.lutece.portal.service.util.AppException;
91  import fr.paris.lutece.portal.service.util.AppLogService;
92  import fr.paris.lutece.portal.service.util.AppPropertiesService;
93  import fr.paris.lutece.portal.web.l10n.LocaleService;
94  
95  /**
96   * Task notify appointment
97   * 
98   * @author Mairie de Paris
99   *
100  */
101 public class TaskNotifyReminder extends SimpleTask
102 {
103 
104     // mark
105     private static final String MARK_FIRST_NAME = "${firstName}";
106     private static final String MARK_LAST_NAME = "${lastName}";
107     private static final String MARK_DATE_APP = "${date_appointment}";
108     private static final String MARK_TIME_APP = "${time_appointment}";
109     private static final String MARK_LOCALIZATION = "${localisation}";
110     private static final String MARK_CANCEL_APP = "${url_cancel}";
111     private static final String DEFAULT_PREFIX_SENDER = "@contact-everyone.fr";
112     private static final String DEFAULT_SENDER_SMS = "magali.lemaire@paris.fr";
113     private static final String MARK_REGEX_SMS = "^(06|07)[0-9]{8}$";
114     private static final String USER_AUTO = "auto";
115     private static final String MARK_DURATION_LIMIT = "daemon.reminder.interval";
116 
117     // properties
118     private static final String PROPERTY_MAIL_SENDER_NAME = "genericalert.task_notify_reminder.mailSenderName";
119     private static final String PROPERTY_SENDER_SMS = "genericalert.senderSms";
120     private static final String PROPERTY_PREFIX_SMS_SENDER = "genericalert.prefixSenderSms";
121 
122     private static final String MESSAGE_MARK_DESCRIPTION = "genericalert.task_notify_reminder.description";
123 
124     public static final String FORMAT_TIME = "HH:mm";
125 
126     // service
127     private final StateService _stateService = SpringContextService.getBean( StateService.BEAN_SERVICE );
128     @Inject
129     @Named( TaskNotifyReminderConfigService.BEAN_SERVICE )
130     private ITaskConfigService _taskNotifyReminderConfigService;
131 
132     @Inject
133     private IActionService _actionService;
134 
135     @Inject
136     private ITaskService _taskService;
137 
138     @Inject
139     private IResourceHistoryService _resourceHistoryService;
140 
141     @Inject
142     private IResourceWorkflowService _resourceWorkflowService;
143 
144     IWorkflowService _workflowService = SpringContextService.getBean( fr.paris.lutece.plugins.workflowcore.service.workflow.WorkflowService.BEAN_SERVICE );
145 
146     @Override
147     public void processTask( int nIdResourceHistory, HttpServletRequest request, Locale locale )
148     {
149         // elle est déclanché juste pour garder une trace dans l'historique du
150         // workflow
151     }
152 
153     public void sendReminder( int nIdResource, String strResourceType, int nIdAction, int nIdWorkflow )
154     {
155         ITask task = null;
156         List<ITask> listActionTasks = _taskService.getListTaskByIdAction( nIdAction, Locale.getDefault( ) );
157         for ( ITask tsk : listActionTasks )
158         {
159             if ( tsk.getTaskType( ) != null && tsk.getTaskType( ).getBeanName( ) != null
160                     && tsk.getTaskType( ).getBeanName( ).equals( "genericalert.taskNotifyReminder" ) )
161             {
162                 task = tsk;
163             }
164         }
165         if ( task != null )
166         {
167             Action action = _actionService.findByPrimaryKey( nIdAction );
168             TaskNotifyReminderConfig config = null;
169             Date date = new Date( );
170             Calendar calendar = new GregorianCalendar( );
171             calendar.setTime( date );
172             Timestamp timestampDay = new Timestamp( calendar.getTimeInMillis( ) );
173             AppointmentDTO appointment = AppointmentService.buildAppointmentDTOFromIdAppointment( nIdResource );
174 
175             if ( appointment != null )
176             {
177                 config = TaskNotifyReminderConfigHome.findByIdForm( task.getId( ), appointment.getIdForm( ) );
178             }
179 
180             if ( config != null )
181             {
182                 for ( int stateBefore : action.getListIdStateBefore( ) ) { 
183                     List<ReminderAppointment> listReminders = null;
184                     if ( FormService.findFormLightByPrimaryKey( appointment.getIdForm( ) ).getIsActive( ) )
185                     {
186                         Timestamp timeStartDate = Timestamp.valueOf( appointment.getStartingDateTime( ) );
187                         State stateAppointment = _stateService.findByResource( appointment.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE,
188                                 nIdWorkflow );
189                         if ( timeStartDate.getTime( ) > timestampDay.getTime( ) && stateAppointment != null && stateAppointment.getId( ) == stateBefore )
190                         {
191                             long minutes = Math.abs( TimeUnit.MILLISECONDS.toMinutes( timestampDay.getTime( ) - timeStartDate.getTime( ) ) );
192                             /*
193                             * long lDiffTimeStamp = Math.abs( timestampDay.getTime( ) - timeStartDate.getTime( ) ); int nDays = (int) lDiffTimeStamp / ( 1000 * 60
194                             * * 60 * 24 ); int nDiffHours = ( (int) lDiffTimeStamp / ( 60 * 60 * 1000 ) % 24 ) + ( nDays * 24 ); int nDiffMin = ( nDiffHours * 60 )
195                             * + (int) ( lDiffTimeStamp / ( 60 * 1000 ) % 60 );
196                             */
197                             if ( config.getNbAlerts( ) > 0 )
198                             {
199                                 listReminders = config.getListReminderAppointment( );
200                             }
201 
202                             for ( ReminderAppointment reminder : listReminders )
203                             {
204                                 sendReminder( appointment, reminder, timeStartDate, minutes, nIdWorkflow, config, strResourceType, nIdAction );
205                             }
206                         }
207                     }
208                 }
209             }
210         }
211     }
212 
213     /**
214      * 
215      * @param appointment
216      *            the appointment
217      * @param reminder
218      *            the reminder
219      * @param startAppointment
220      *            startAppointment date of start appointment
221      * @param nDiffMin
222      *            diffrence time in minutes
223      * @param form
224      *            the appointment form
225      * @param config
226      *            the task config
227      */
228     private void sendReminder( AppointmentDTO appointment, ReminderAppointment reminder, Date startAppointment, long nDiffMin, int nIdWorkflow,
229             TaskNotifyReminderConfig config, String strResourceType, int nIdAction )
230     {
231         int nInterval = Integer.parseInt( AppPropertiesService.getProperty( MARK_DURATION_LIMIT ) );
232 
233         long nMinTime = ( reminder.getTimeToAlert( ) * 60 * 24 ) - nInterval;
234         long nMaxTime = ( reminder.getTimeToAlert( ) * 60 * 24 ) + nInterval;
235 
236         if ( nDiffMin <= nMaxTime && nDiffMin >= nMinTime
237                 && ( ( appointment.getNotification( ) == 0 ) || ( appointment.getNotification( ) != ( reminder.getRank( ) ) ) ) )
238         {
239             boolean bNotified = false;
240             Locale locale = LocaleService.getDefault( );
241             String strSenderMail = MailService.getNoReplyEmail( );
242             String strSenderName = I18nService.getLocalizedString( PROPERTY_MAIL_SENDER_NAME, locale );
243             String strEmailCc = reminder.getEmailCc( );
244 
245             String strEmailText = reminder.getEmailAlertMessage( );
246             String strSmsText = reminder.getSmsAlertMessage( );
247 
248             if ( strEmailText != null && !strEmailText.isEmpty( ) )
249             {
250                 strEmailText = getMessageAppointment( strEmailText, appointment );
251             }
252             if ( strSmsText != null && !strSmsText.isEmpty( ) )
253             {
254                 strSmsText = getMessageAppointment( strSmsText, appointment );
255             }
256             User user = UserService.findUserById( appointment.getIdUser( ) );
257             if ( reminder.isEmailNotify( ) && !StringUtils.isEmpty( user.getEmail( ) ) )
258             {
259                 try
260                 {
261                     MailService.sendMailHtml( user.getEmail( ), strEmailCc, StringUtils.EMPTY, strSenderName, strSenderMail, reminder.getAlertSubject( ),
262                             strEmailText );
263                     bNotified = true;
264                 }
265                 catch( Exception e )
266                 {
267                     AppLogService.info( "CATCH sending MAIL" );
268                     AppLogService.error( "AppointmentReminderDaemon - Error sending reminder alert MAIL to : {}", e.getMessage( ), e );
269                 }
270             }
271             // AppLogService.info( "SMS : " + reminder.isSmsNotify( ) );
272             if ( reminder.isSmsNotify( ) && reminder.getNumberPhone( ) != null )
273             {
274                 String strRecipient = getSmsFromAppointment( appointment, reminder );
275 
276                 if ( !strRecipient.isEmpty( ) && strRecipient.matches( MARK_REGEX_SMS ) )
277                 {
278                     try
279                     {
280 
281                         String strDefaultRecipientSms = AppPropertiesService.getProperty( PROPERTY_PREFIX_SMS_SENDER, DEFAULT_PREFIX_SENDER );
282                         String strSenderSms = AppPropertiesService.getProperty( PROPERTY_SENDER_SMS, DEFAULT_SENDER_SMS );
283 
284                         strRecipient += strDefaultRecipientSms;
285 
286                         MailService.sendMailHtml( strRecipient, strSenderName, strSenderSms, reminder.getAlertSubject( ), strSmsText );
287                         bNotified = true;
288                     }
289                     catch( Exception e )
290                     {
291                         AppLogService.info( "CATCH sending reminder alert SMS: " );
292                         AppLogService.error( "AppointmentReminderDaemon - Error sending reminder alert SMS to : {} {}", strRecipient, e.getMessage( ), e );
293                     }
294                 }
295             }
296             if ( bNotified )
297             {
298                 appointment.setNotification( reminder.getRank( ) );
299                 try
300                 {
301                     AppointmentService.updateAppointment( appointment );
302                     doChangeState( config, reminder, nIdWorkflow, appointment );
303                 }
304                 catch( Exception e )
305                 {
306                     AppLogService.info( "CATCH CHANGING STATE : " );
307                     throw new AppException( e.getMessage( ), e );
308 
309                 }
310             }
311         }
312     }
313 
314     /**
315      * Get sms number
316      * 
317      * @param appointment
318      *            the appointment
319      * @param reminder
320      *            the reminder task
321      * @return the sms number
322      */
323     private String getSmsFromAppointment( AppointmentDTO appointment, ReminderAppointment reminder )
324     {
325         String strPhoneNumber = StringUtils.EMPTY;
326         EntryFilter entryFilter = new EntryFilter( );
327         entryFilter.setIdResource( appointment.getIdForm( ) );
328 
329         List<Integer> listIdResponse = AppointmentResponseService.findListIdResponse( appointment.getIdAppointment( ) );
330         List<Response> listResponses = new ArrayList<>( listIdResponse.size( ) );
331         for ( int nIdResponse : listIdResponse )
332         {
333             Response response = ResponseHome.findByPrimaryKey( nIdResponse );
334             if ( response != null )
335             {
336                 listResponses.add( response );
337             }
338         }
339 
340         List<Entry> listEntries = EntryHome.getEntryList( entryFilter );
341         for ( Entry entry : listEntries )
342         {
343             IEntryTypeService entryTypeService = EntryTypeServiceManager.getEntryTypeService( entry );
344 
345             if ( entryTypeService instanceof EntryTypePhone )
346             {
347                 for ( Response response : listResponses )
348                 {
349                     if ( ( response.getEntry( ).getIdEntry( ) == entry.getIdEntry( ) ) && StringUtils.isNotBlank( response.getResponseValue( ) )
350                             && entry.getTitle( ).equals( reminder.getNumberPhone( ) ) )
351                     {
352                         strPhoneNumber = response.getResponseValue( );
353 
354                         break;
355                     }
356                 }
357 
358                 if ( StringUtils.isNotEmpty( strPhoneNumber ) )
359                 {
360                     break;
361                 }
362             }
363         }
364         return strPhoneNumber;
365     }
366 
367     /**
368      * Get message text
369      * 
370      * @param msg
371      *            the text message
372      * @param appointment
373      *            the appointment
374      * @return the text message
375      */
376     private String getMessageAppointment( String msg, AppointmentDTO appointment )
377     {
378         DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern( FORMAT_TIME );
379         String strText = StringUtils.EMPTY;
380         User user = UserService.findUserById( appointment.getIdUser( ) );
381         Localization localization = LocalizationService.findLocalizationWithFormId( appointment.getIdForm( ) );
382         String strLocation = StringUtils.EMPTY;
383         if ( localization != null && localization.getAddress( ) != null )
384         {
385             strLocation = localization.getAddress( );
386         }
387         strText = msg.replace( MARK_FIRST_NAME, user.getFirstName( ) );
388         strText = strText.replace( MARK_LAST_NAME, user.getLastName( ) );
389         strText = strText.replace( MARK_DATE_APP, appointment.getDateOfTheAppointment( ) );
390         strText = strText.replace( MARK_TIME_APP, appointment.getStartingDateTime( ).format( timeFormatter ) );
391         strText = strText.replace( MARK_LOCALIZATION, strLocation );
392         strText = strText.replace( MARK_CANCEL_APP, AppointmentApp.getCancelAppointmentUrl( appointment ) );
393 
394         return strText;
395     }
396 
397     /**
398      * 
399      * @param config
400      *            the config task
401      * @param reminder
402      *            the reminder task
403      * @param form
404      *            the appointment form
405      * @param appointment
406      *            the appointment
407      */
408     private void doChangeState( TaskNotifyReminderConfig config, ReminderAppointment reminder, int nIdWorkflow, Appointment appointment )
409     {
410         Locale locale = I18nService.getDefaultLocale( );
411         ITask task = _taskService.findByPrimaryKey( config.getIdTask( ), locale );
412 
413         if ( task != null )
414         {
415 
416             State state = _stateService.findByPrimaryKey( reminder.getIdStateAfter( ) );
417             Action action = _actionService.findByPrimaryKey( task.getAction( ).getId( ) );
418 
419             if ( ( state != null ) && ( action != null ) )
420             {
421 
422                 // Create Resource History
423                 ResourceHistory resourceHistory = new ResourceHistory( );
424                 resourceHistory.setIdResource( appointment.getIdAppointment( ) );
425                 resourceHistory.setResourceType( Appointment.APPOINTMENT_RESOURCE_TYPE );
426                 resourceHistory.setAction( action );
427                 resourceHistory.setWorkFlow( action.getWorkflow( ) );
428                 resourceHistory.setCreationDate( WorkflowUtils.getCurrentTimestamp( ) );
429                 resourceHistory.setUserAccessCode( USER_AUTO );
430                 _resourceHistoryService.create( resourceHistory );
431 
432                 // Update Resource
433                 ResourceWorkflow resourceWorkflow = _resourceWorkflowService.findByPrimaryKey( appointment.getIdAppointment( ),
434                         Appointment.APPOINTMENT_RESOURCE_TYPE, nIdWorkflow );
435 
436                 resourceWorkflow.setState( state );
437                 _resourceWorkflowService.update( resourceWorkflow );
438                 // Execute the relative tasks of the state in the workflow
439                 // We use AutomaticReflexiveActions because we don't want to change the state of the resource by executing actions.
440                 _workflowService.doProcessAutomaticReflexiveActions( appointment.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE, state.getId( ),
441                         null, locale );
442             }
443         }
444     }
445 
446     /**
447      * {@inheritDoc}
448      */
449     @Override
450     public void doRemoveConfig( )
451     {
452         _taskNotifyReminderConfigService.remove( this.getId( ) );
453     }
454 
455     @Override
456     public String getTitle( Locale locale )
457     {
458         return I18nService.getLocalizedString( MESSAGE_MARK_DESCRIPTION, locale );
459     }
460 
461 }