View Javadoc
1   /*
2    * Copyright (c) 2002-2023, 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.workflow.modules.appointmentants.service;
35  
36  import java.io.IOException;
37  import java.io.UnsupportedEncodingException;
38  import java.net.URLEncoder;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collections;
42  import java.util.HashMap;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  
47  import javax.inject.Inject;
48  import javax.inject.Named;
49  import javax.servlet.http.HttpServletRequest;
50  
51  import org.apache.commons.collections4.CollectionUtils;
52  import org.apache.commons.lang.StringUtils;
53  import org.apache.commons.lang3.ArrayUtils;
54  
55  import com.fasterxml.jackson.databind.JsonNode;
56  import com.fasterxml.jackson.databind.ObjectMapper;
57  
58  import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
59  import fr.paris.lutece.plugins.appointment.business.localization.Localization;
60  import fr.paris.lutece.plugins.appointment.service.AppointmentResponseService;
61  import fr.paris.lutece.plugins.appointment.service.AppointmentService;
62  import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
63  import fr.paris.lutece.plugins.appointment.service.LocalizationService;
64  import fr.paris.lutece.plugins.appointment.web.AppointmentApp;
65  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
66  import fr.paris.lutece.plugins.genericattributes.business.Response;
67  import fr.paris.lutece.plugins.workflow.modules.appointmentants.business.TaskAntsAppointmentConfigDAO;
68  import fr.paris.lutece.plugins.workflow.modules.appointmentants.business.history.TaskAntsAppointmentHistory;
69  import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsAddAppointmentResponsePOJO;
70  import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsDeleteAppointmentResponsePOJO;
71  import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsStatusResponsePOJO;
72  import fr.paris.lutece.plugins.workflow.modules.appointmentants.service.rest.TaskAntsAppointmentRest;
73  import fr.paris.lutece.plugins.workflow.modules.appointmentants.service.rest.TaskAntsAppointmentRestConstants;
74  import fr.paris.lutece.portal.service.util.AppLogService;
75  import fr.paris.lutece.portal.service.util.AppPropertiesService;
76  import fr.paris.lutece.util.httpaccess.HttpAccessException;
77  import fr.paris.lutece.util.url.UrlItem;
78  
79  /**
80   * 
81   * Class containing useful methods to handle ANTS related tasks
82   * 
83   */
84  public class TaskAntsAppointmentService implements ITaskAntsAppointmentService {
85  
86  	public static final String BEAN_SERVICE = WorkflowAppointmentAntsPlugin.PLUGIN_NAME + ".taskAntsAppointmentService";
87  
88  	@Inject
89  	@Named( TaskAntsAppointmentConfigDAO.BEAN_NAME )
90  	private TaskAntsAppointmentConfigDAO _task_ants_appointment_dao;	
91  
92  	/**
93  	 * ANTS' API URLs
94  	 */
95  	private static final String ANTS_BASE_URL =
96  			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL );
97  	private static final String ANTS_STATUS_URL =
98  			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_STATUS_APPOINTMENT );
99  
100 	/**
101 	 * ANTS' API URL Parameters
102 	 */
103 	private static final String URL_PARAMETER_APPLICATION_ID =
104 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPLICATION_ID );
105 	private static final String URL_PARAMETER_APPLICATION_IDS =
106 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPLICATION_IDS );
107 	private static final String URL_PARAMETER_MANAGEMENT_URL =
108 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_MANAGEMENT_URL );
109 	private static final String URL_PARAMETER_MEETING_POINT =
110 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_MEETING_POINT );
111 	private static final String URL_PARAMETER_APPOINTMENT_DATE =
112 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPOINTMENT_DATE );
113 
114 	/**
115 	 * Value of the ANTS API Token
116 	 */
117 	private static final String PROPERTY_API_OPT_AUTH_TOKEN_VALUE =
118 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_TOKEN_VALUE );
119 
120 	/**
121 	 * Status value of an ANTS appointment ("validated", "consumed", etc.)
122 	 */
123 	private static final String STATUS_VALIDATED =
124 			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPOINTMENT_VALIDATED );
125 
126 	/**
127 	 * Variables for general use
128 	 */
129 	private static final String APPLICATION_NUMBERS_SEPARATOR =
130 			AppPropertiesService.getProperty( "ants.api.application.numbers.separator" );
131 
132 	private static final String PARIS_USER_ACCOUNT_URL =
133 			AppPropertiesService.getProperty( "paris.user.account.url" );
134 
135 	/**
136 	 * Variables used to save / retrieve specific details of an appointment
137 	 */
138 	public static final String KEY_URL = "url";
139 	public static final String KEY_LOCATION = "location";
140 	public static final String KEY_DATE = "date";
141 
142 	private TaskAntsAppointmentService( )
143 	{
144 	}
145 
146 	/**
147 	 * Create an appointment in the ANTS database
148 	 * 
149 	 * @param request
150 	 * 				request to use
151 	 * @param idAppointment
152 	 * 				ID of the appointment that will be processed
153 	 * @param idTask
154 	 * 				ID of the workflow task calling this method
155 	 * @param antsAppointmentHistory
156 	 * 				Instance of TaskAntsAppointmentHistory object used to save the task's history
157 	 * @return
158 	 * 				true if it was successfully created, returns false if it failed
159 	 */
160 	@Override
161 	public boolean createAntsAppointment( HttpServletRequest request, int idAppointment, int idTask, TaskAntsAppointmentHistory antsAppointmentHistory )
162 	{
163 		Map<String, String> applicationContent = getAppointmentData( request, idAppointment, false );
164 
165 		boolean isAppointmentCreated = false;
166 
167 		// Get the ANTS application numbers from the appointment's Responses
168 		String strAntsApplicationNumbers = getAntsApplicationValuesFromResponse(
169 				idAppointment,
170 				getAntsApplicationFieldId( idTask )
171 				);
172 
173 		// Split the potential ANTS application values retrieved from the appointment's Responses
174 		List<String> applicationNumberList = splitAntsApplicationValues( strAntsApplicationNumbers, APPLICATION_NUMBERS_SEPARATOR );
175 
176 		// If the appointment has no application number(s), then stop the task
177 		if( CollectionUtils.isEmpty( applicationNumberList ) )
178 		{
179 			AppLogService.info( "{} - Appointment with ID {} has no ANTS number", BEAN_SERVICE, idAppointment );
180 			// Return true so the task stops with a positive result
181 			return true;
182 		}
183 
184 		// Set the ANTS application numbers in the task's history
185 		antsAppointmentHistory.setAntsApplicationNumbers( strAntsApplicationNumbers );
186 
187 		// Check if the application number used are valid and allow appointments creation
188 		if( isApplicationNumberListValidForCreation( idAppointment, applicationNumberList ) ) {
189 
190 			// For each application number available, create a new ANTS appointment
191 			for( String appplicationNumber : applicationNumberList ) {
192 
193 				// Build the ANTS URL used to create a new appointment
194 				String antsURL = buildAntsAddAppointmentUrl(
195 						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL),
196 						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_ADD_APPOINTMENT),
197 						appplicationNumber,
198 						applicationContent.get( KEY_URL ),
199 						applicationContent.get( KEY_LOCATION ),
200 						applicationContent.get( KEY_DATE )
201 						);
202 				try {
203 					// Create the appointment on the ANTS database
204 					isAppointmentCreated = addAntsAppointmentRestCall( antsURL );
205 
206 					AppLogService.debug(
207 							"{} ANTS appointment with number '{}' was {} for appointment with ID {}",
208 							BEAN_SERVICE,
209 							appplicationNumber,
210 							isAppointmentCreated ? "created" : "not created",
211 							idAppointment
212 							);
213 
214 					// If the appointment was not created
215 					if( !isAppointmentCreated )
216 					{
217 						return isAppointmentCreated;
218 					}
219 				}
220 				catch( Exception e )
221 				{
222 					AppLogService.error( BEAN_SERVICE, e );
223 				}
224 			}
225 		}
226 		return isAppointmentCreated;
227 	}
228 
229 	/**
230 	 * Remove an appointment from the ANTS database
231 	 * 
232 	 * @param request
233 	 * 				request to use
234 	 * @param idAppointment
235 	 * 				ID of the appointment that will be processed
236 	 * @param idTask
237 	 * 				ID of the workflow task calling this method
238 	 * @param antsAppointmentHistory
239 	 * 				Instance of TaskAntsAppointmentHistory object used to save the task's history
240 	 * @return
241 	 * 				true if it was successfully deleted, returns false if it failed
242 	 */
243 	@Override
244 	public boolean deleteAntsAppointment( HttpServletRequest request, int idAppointment, int idTask, TaskAntsAppointmentHistory antsAppointmentHistory )
245 	{
246 		Map<String, String> applicationContent = getAppointmentData( request, idAppointment, true );
247 
248 		boolean isAppointmentDeleted = false;
249 
250 		// Get the ANTS application numbers from the appointment's Responses
251 		String strAntsApplicationNumbers = getAntsApplicationValuesFromResponse(
252 				idAppointment,
253 				getAntsApplicationFieldId( idTask )
254 				);
255 
256 		// Split the potential ANTS application values retrieved from the appointment's Responses
257 		List<String> applicationNumberList = splitAntsApplicationValues( strAntsApplicationNumbers, APPLICATION_NUMBERS_SEPARATOR );
258 
259 		// If the appointment has no application number(s), then stop the task
260 		if( CollectionUtils.isEmpty( applicationNumberList ) )
261 		{
262 			AppLogService.info( "{} - Appointment with ID {} has no ANTS number", BEAN_SERVICE, idAppointment );
263 			// Return true so the task stops with a positive result
264 			return true;
265 		}
266 
267 		// Set the ANTS application numbers in the task's history
268 		antsAppointmentHistory.setAntsApplicationNumbers( strAntsApplicationNumbers );
269 
270 		// Check if the application numbers used are valid and still allow the appointments to be deleted
271 		if( isApplicationNumberListValidForDeletion( idAppointment, applicationNumberList ) ) {
272 			// For each application number available, delete any existing ANTS appointment
273 			for( String appplicationNumber : applicationNumberList ) {
274 
275 				// Build the ANTS URL used to delete an appointment
276 				String antsURL = buildAntsDeleteAppointmentUrl(
277 						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL),
278 						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_DELETE_APPOINTMENT),
279 						appplicationNumber,
280 						applicationContent.get( KEY_LOCATION ),
281 						applicationContent.get( KEY_DATE )
282 						);
283 				try {
284 					// Delete the appointment from the ANTS database
285 					isAppointmentDeleted = deleteAntsAppointmentRestCall( antsURL );
286 
287 					AppLogService.debug(
288 							"{} ANTS appointment with number '{}' was {} for appointment with ID {}",
289 							BEAN_SERVICE,
290 							appplicationNumber,
291 							isAppointmentDeleted ? "deleted" : "not deleted",
292 							idAppointment
293 							);
294 
295 					// If the appointment was not deleted successfully
296 					if( !isAppointmentDeleted )
297 					{
298 						return isAppointmentDeleted;
299 					}
300 				}
301 				catch ( Exception e )
302 				{
303 					AppLogService.error( BEAN_SERVICE, e );
304 				}
305 			}
306 		}
307 		return isAppointmentDeleted;
308 	}
309 
310 	/**
311 	 * Check if an appointment was created from the front office or from the back office
312 	 * 
313 	 * @param appointment
314 	 * 				The appointment to check
315 	 * @return
316 	 * 				true if it was created by a user in the front office, returns false otherwise
317 	 */
318 	public static boolean isAppointmentCreatedInFrontOffice( Appointment appointment )
319 	{
320 		/* If the appointment's "AdminUserCreate" field has no value, then it is considered
321 		 * that it was created by a user in the front office 
322 		 * */
323 		return appointment.getAdminUserCreate( ) == null ||
324 				appointment.getAdminUserCreate( ).isEmpty( );
325 	}
326 
327 	/**
328 	 * Build the URL used to add an appointment in the ANTS DB
329 	 * 
330 	 * @param baseUrl
331 	 * 				The base URL of the ANTS API
332 	 * @param addAppointmentUrl
333 	 * 				The ANTS API's endpoint used to add appointments
334 	 * @param applicationId
335 	 * 				The ANTS application number used to create the appointment
336 	 * @param managementUrl
337 	 * 				The URL used to access the appointment's web page
338 	 * @param meetingPoint
339 	 * 				The location of the appointment
340 	 * @param dateTime
341 	 * 				The date and time of the appointment
342 	 * @return
343 	 * 				The complete URL used to create this specific appointment in
344 	 * 				the ANTS database
345 	 */
346 	public static String buildAntsAddAppointmentUrl( String baseUrl, String addAppointmentUrl, String applicationId,
347 			String managementUrl, String meetingPoint, String dateTime )
348 	{
349 		StringBuilder antsApiUrl =  new StringBuilder( baseUrl ).
350 				append( addAppointmentUrl );
351 
352 		UrlItem urlItem = new UrlItem( antsApiUrl.toString( ) );
353 		urlItem.addParameter(URL_PARAMETER_APPLICATION_ID, applicationId );
354 		urlItem.addParameter(URL_PARAMETER_MANAGEMENT_URL, managementUrl );
355 		urlItem.addParameter(URL_PARAMETER_MEETING_POINT, meetingPoint );
356 		urlItem.addParameter(URL_PARAMETER_APPOINTMENT_DATE, dateTime );
357 
358 		return urlItem.getUrl( );
359 	}
360 
361 	/**
362 	 * Use the ANTS API to add an appointment to their database
363 	 * 
364 	 * @param antsUrl
365 	 * 				URL used to make the REST call
366 	 * @return
367 	 * 				true if the appointment was added successfully, returns false otherwise
368 	 * @throws HttpAccessException
369 	 * @throws IOException
370 	 */
371 	public static boolean addAntsAppointmentRestCall( String antsUrl ) throws HttpAccessException, IOException
372 	{
373 		String response = TaskAntsAppointmentRest.addAntsAppointment( antsUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );
374 
375 		return isAppointmentCreationSuccessful( response );
376 	}
377 
378 	/**
379 	 * Build the URL used to delete an appointment from the ANTS DB
380 	 * 
381 	 * @param baseUrl
382 	 * 				The base URL of the ANTS API
383 	 * @param deleteAppointmentUrl
384 	 * 				The ANTS API's endpoint used to delete an appointment
385 	 * @param applicationId
386 	 * 				The ANTS application number used to identify the
387 	 * 				appointment to delete
388 	 * @param meetingPoint
389 	 * 				The location of the appointment
390 	 * @param dateTime
391 	 * 				The date and time of the appointment
392 	 * @return
393 	 * 				The complete URL used to delete this specific appointment
394 	 * 				from the ANTS database
395 	 */
396 	public static String buildAntsDeleteAppointmentUrl( String baseUrl, String deleteAppointmentUrl, String applicationId,
397 			String meetingPoint, String dateTime )
398 	{
399 		StringBuilder antsApiUrl =  new StringBuilder( baseUrl ).
400 				append( deleteAppointmentUrl );
401 
402 		UrlItem urlItem = new UrlItem( antsApiUrl.toString( ) );
403 		urlItem.addParameter(URL_PARAMETER_APPLICATION_ID, applicationId );
404 		urlItem.addParameter(URL_PARAMETER_MEETING_POINT, meetingPoint );
405 		urlItem.addParameter(URL_PARAMETER_APPOINTMENT_DATE, dateTime );
406 
407 		return urlItem.getUrl( );
408 	}
409 
410 	/**
411 	 * Use the ANTS API to delete an appointment from their database
412 	 * 
413 	 * @param antsUrl
414 	 * 				URL used to make the REST call
415 	 * @return
416 	 * 				true if the appointment was deleted successfully, returns false otherwise
417 	 * @throws HttpAccessException
418 	 * @throws IOException
419 	 */
420 	public static boolean deleteAntsAppointmentRestCall( String antsUrl ) throws HttpAccessException, IOException
421 	{
422 		String response = TaskAntsAppointmentRest.deleteAntsAppointment( antsUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );
423 
424 		return isAppointmentDeletionSuccessful( response );
425 	}
426 
427 	/**
428 	 * Retrieve the details of the current appointment (user name, email, date, etc.)
429 	 * 
430 	 * @param request
431 	 * 				The request from the current context
432 	 * @param idAppointment
433 	 * 				The ID of the appointment to process
434 	 * @param isDeletingAntsAppointment
435 	 * 				Whether the appointment is getting deleted (true) or if it is being created (false)
436 	 * @return
437 	 * 				A <Key, Value> list of the current appointment's URL,
438 	 * 				location and date
439 	 */
440 	public static Map<String, String> getAppointmentData( HttpServletRequest request, int idAppointment, boolean isDeletingAppointment )
441 	{
442 		Map<String, String> appointmentDataMap = new HashMap<>( );
443 		AppointmentDTO appointmentDto = null;
444 
445 		// Check if the current appointment is being deleted
446 		if( isDeletingAppointment )
447 		{
448 			// Get the appointement's previous data, in case it is being rescheduled
449 			appointmentDto = getOldAppointment( request );
450 
451 			if( appointmentDto == null )
452 			{
453 				// The appointment isn't being rescheduled, so we retrieve its current data
454 				appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( idAppointment );
455 			}
456 		}
457 		else
458 		{
459 			// The appointment is being created, so we retrieve its data
460 			appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( idAppointment );
461 		}
462 
463 		// Get the URL of the user's account on PARIS' web site, and encode it
464 		appointmentDataMap.put(
465 				KEY_URL,
466 				cleanUrl( PARIS_USER_ACCOUNT_URL ) );
467 
468 		// Get the appointment's location
469 		String appointmentLocation = "";
470 
471 		if( appointmentDto != null ) {
472 			Localization localization = LocalizationService.findLocalizationWithFormId( appointmentDto.getIdForm( ) );
473 			if( localization != null && localization.getAddress( ) != null )
474 			{
475 				appointmentLocation = localization.getAddress( );
476 			}
477 		}
478 		appointmentDataMap.put(
479 				KEY_LOCATION,
480 				cleanUrl( appointmentLocation ).replace( "+" , "%20" ) );
481 
482 		// Get the appointment's date and time
483 		String appointmentDateTime = "";
484 
485 		if( appointmentDto != null )
486 		{
487 			String startingDateTime = appointmentDto.getStartingDateTime( ).toString( );
488 
489 			// Encode the date and time so they fit properly in a URL and encode the ':' characters
490 			appointmentDateTime = cleanUrl( startingDateTime ).replace( "+" , "%20" );
491 		}
492 		appointmentDataMap.put(
493 				KEY_DATE,
494 				appointmentDateTime );
495 
496 		return appointmentDataMap;
497 	}
498 
499 	/**
500 	 * Get the AppointmentDTO containing the previous data of an appointment. It is retrieved
501 	 * from the request's attributes
502 	 * 
503 	 * @param request
504 	 * 				The request containing the AppointmentDTO in its attributes
505 	 * @return
506 	 * 				The AppointmentDTO Object containing the previous data if it was found,
507 	 * 				returns null otherwise
508 	 */
509 	private static AppointmentDTO getOldAppointment( HttpServletRequest request )
510 	{
511 		AppointmentDTO oldAppointment = null;
512 
513 		try
514 		{
515 			// Retrieve the previous appointment from the request's parameters
516 			oldAppointment = ( AppointmentDTO ) request.getAttribute( AppointmentUtilities.OLD_APPOINTMENT_DTO );
517 		}
518 		catch ( Exception e )
519 		{
520 			AppLogService.info( BEAN_SERVICE + " removing appointment from ants database: {}", e.getMessage( ) );
521 		}
522 		return oldAppointment;
523 	}
524 
525 	/**
526 	 * Get the status of every ANTS application numbers given as parameter
527 	 * 
528 	 * @param applicationNumberList
529 	 * 				List of the application numbers for which the status
530 	 * 				will be retrieved
531 	 * @return
532 	 * 				A list of Objects representing the status of the given ANTS
533 	 * 				application	numbers, returns an empty List if no element was found
534 	 * @throws HttpAccessException
535 	 */
536 	public static List<AntsStatusResponsePOJO> getAntsStatusResponseAsObjects( List<String> applicationNumberList )
537 			throws HttpAccessException
538 	{
539 		String getStatusUrl = buildAntsGetStatusAppointmentUrl( applicationNumberList );
540 
541 		String response = "";
542 
543 		List<AntsStatusResponsePOJO> statusObjectsList = new ArrayList<>( );
544 
545 		response = TaskAntsAppointmentRest.getAntsAppointmentStatus( getStatusUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );
546 		AppLogService.debug( "{} - ANTS GET STATUS request successful - Response: {}", BEAN_SERVICE, response );
547 
548 		// If the HTTP call was made and returned a response
549 		if( StringUtils.isNotBlank( response ) )
550 		{
551 			try
552 			{
553 				statusObjectsList = getStatusResponseAsObject( response );
554 			}
555 			catch( IOException e)
556 			{
557 				AppLogService.error( BEAN_SERVICE, e );
558 			}
559 		}
560 		return statusObjectsList;
561 	}
562 
563 	/**
564 	 * Build the URL used to get the status of specific ANTS appointments
565 	 * 
566 	 * @param applicationIdsList
567 	 * 				The List of ANTS application numbers to use
568 	 * @return
569 	 * 				The complete URL used to check the status of appointments
570 	 * 				from the ANTS database
571 	 */
572 	public static String buildAntsGetStatusAppointmentUrl( List<String> applicationIdsList )
573 	{
574 		// Build the base ANTS API URL used to retrieve the status of appointments
575 		StringBuilder antsApisUrl = new StringBuilder(
576 				ANTS_BASE_URL ).append( ANTS_STATUS_URL );
577 
578 		UrlItem urlItem = new UrlItem( antsApisUrl.toString( ) );
579 
580 		// Add every ANTS application number to the URL's parameters
581 		for( String applicationId : applicationIdsList )
582 		{
583 			urlItem.addParameter( URL_PARAMETER_APPLICATION_IDS, applicationId );
584 		}
585 		return urlItem.getUrl();
586 	}
587 
588 	/**
589 	 * Check if the status of the given application numbers are valid and allow to add
590 	 * new appointments ('validated' status and empty list of appointments)
591 	 * 
592 	 * @param idAppointment
593 	 * 				ID of the appointment being processed
594 	 * @param applicationNumberList
595 	 * 				List of ANTS application numbers to check for validity
596 	 * @return
597 	 * 				true if all the application numbers are valid, false otherwise
598 	 */
599 	public static boolean isApplicationNumberListValidForCreation( int idAppointment, List<String> applicationNumberList ) 
600 	{
601 		List<AntsStatusResponsePOJO> statusResponseList = null;
602 		try
603 		{
604 			// Get the status of the given ANTS application number(s)
605 			statusResponseList = getAntsStatusResponseAsObjects( applicationNumberList );
606 		}
607 		catch ( Exception e )
608 		{
609 			AppLogService.error( BEAN_SERVICE, e );
610 			return false;
611 		}
612 
613 		if( CollectionUtils.isEmpty( statusResponseList ) )
614 		{
615 			AppLogService.info( "{} - No status retrieved for the ANTS numbers {}",
616 					BEAN_SERVICE, Arrays.toString( applicationNumberList.toArray( ) ) );
617 			return false;
618 		}
619 
620 		// Check the validity of each application number's status and appointments
621 		for( AntsStatusResponsePOJO statusResponse : statusResponseList )
622 		{
623 			String statusAntsNumber = statusResponse.getStatus( );
624 			Object[] listAntsNumberAppointments = statusResponse.getAppointments( );
625 
626 			/* If the application number hasn't been validated, or if it already has
627 			 * appointments tied to it, then we shouldn't create any appointment
628 			 * */
629 			if( !StringUtils.equals( statusAntsNumber, STATUS_VALIDATED ) ||
630 					ArrayUtils.isNotEmpty( listAntsNumberAppointments ) )
631 			{
632 				AppLogService.error(
633 						"{} - ANTS appointment not valid for creation: Appointment {} with ANTS number '{}' has a status '{}' and {} appointment(s)",
634 						BEAN_SERVICE, idAppointment, Arrays.toString( applicationNumberList.toArray( ) ), statusAntsNumber, listAntsNumberAppointments.length );
635 				return false;
636 			}
637 		}
638 		return true;
639 	}
640 
641 	/**
642 	 * Check if the status of the given application numbers are valid and allow to delete
643 	 * existing appointments ('validated' status and at least 1 element in their list of appointment)
644 	 * 
645 	 * @param idAppointment
646 	 * 				ID of the appointment being processed
647 	 * @param applicationNumberList
648 	 * 				List of ANTS application numbers to check for potential deletion
649 	 * @return
650 	 * 				true if the appointments with the given application numbers can be deleted,
651 	 * 				false otherwise
652 	 */
653 	public static boolean isApplicationNumberListValidForDeletion( int idAppointment, List<String> applicationNumberList )
654 	{
655 		List<AntsStatusResponsePOJO> statusResponseList = null;
656 		try
657 		{
658 			// Get the status of the given ANTS application number(s)
659 			statusResponseList = getAntsStatusResponseAsObjects( applicationNumberList );
660 		}
661 		catch ( Exception e )
662 		{
663 			AppLogService.error( BEAN_SERVICE, e );
664 			return false;
665 		}
666 
667 		if( CollectionUtils.isEmpty( statusResponseList ) )
668 		{
669 			AppLogService.info( "{} - No status retrieved for the ANTS numbers {}",
670 					BEAN_SERVICE, Arrays.toString( applicationNumberList.toArray( ) ) );
671 			return false;
672 		}
673 
674 		// Check the validity of each application number's status and appointments
675 		for( AntsStatusResponsePOJO statusResponse : statusResponseList )
676 		{
677 			String statusAntsNumber = statusResponse.getStatus( );
678 			Object[] listAntsNumberAppointments = statusResponse.getAppointments( );
679 
680 			/* If the application number hasn't been validated, and if it has no
681 			 * appointment tied to it, then we can't delete it
682 			 * */
683 			if( !StringUtils.equals( statusAntsNumber, STATUS_VALIDATED ) ||
684 					ArrayUtils.isEmpty( listAntsNumberAppointments ) )
685 			{
686 				AppLogService.error(
687 						"{} - ANTS appointment not valid for deletion: Appointment {} with ANTS number '{}' has a status '{}' and no appointment",
688 						BEAN_SERVICE, idAppointment, Arrays.toString( applicationNumberList.toArray( ) ), statusAntsNumber );
689 				return false;
690 			}
691 		}
692 		return true;
693 	}
694 
695 	/**
696 	 * Creates a List of {@link AntsStatusResponsePOJO} Objects from a json String containing the status
697 	 * and appointments list that were returned by the ANTS API
698 	 * 
699 	 * @param response
700 	 * 				The content of the HTTP response returned by the ANTS API after getting the status of 
701 	 * 				one or more ANTS application number(s)
702 	 * @return
703 	 * 				A List of AntsStatusResponsePOJO Objects. Each item represents the status of one ANTS
704 	 * 				application number
705 	 * @throws IOException
706 	 */
707 	public static List<AntsStatusResponsePOJO> getStatusResponseAsObject( String response ) throws IOException
708 	{
709 		ObjectMapper mapper = new ObjectMapper( );
710 		JsonNode jsonNode = mapper.readTree( response );
711 
712 		List<AntsStatusResponsePOJO> statusList = new ArrayList<>( );
713 
714 		Iterator<String> fieldNames = jsonNode.fieldNames();
715 
716 		// Build Objects from all the application numbers available
717 		while( fieldNames.hasNext( ) )
718 		{
719 			String fieldName = fieldNames.next( );
720 			JsonNode field = jsonNode.get( fieldName );
721 			statusList.add(
722 					mapper.readerFor( AntsStatusResponsePOJO.class )
723 					.readValue( field.toString() )
724 					);
725 		}
726 		return statusList;
727 	}
728 
729 	/**
730 	 * Properly encode a String containing a URL to make sure it has the proper format (special characters encoding...)
731 	 * 
732 	 * @param urlToClean
733 	 * 				The URL to process
734 	 * @return
735 	 * 				A URL with encoded characters. Returns the initial URL if an error occurred
736 	 */
737 	public static String cleanUrl( String urlToClean )
738 	{
739 		try
740 		{
741 			return URLEncoder.encode( urlToClean, "utf-8" );
742 		}
743 		catch( UnsupportedEncodingException e)
744 		{
745 			AppLogService.error( BEAN_SERVICE, e );
746 			return urlToClean;
747 		}
748 	}
749 
750 	/**
751 	 * Check whether an HTTP response returned a successful result or not when
752 	 * trying to create an appointment with the ANTS API
753 	 * 
754 	 * @param response
755 	 * 				The content of the HTTP response returned by the ANTS API
756 	 * @return
757 	 * 				true if the appointment was created successfully, returns false otherwise
758 	 * @throws IOException
759 	 */
760 	public static boolean isAppointmentCreationSuccessful( String response ) throws IOException
761 	{
762 		ObjectMapper mapper = new ObjectMapper( );
763 
764 		// Convert the content of the response into an Object
765 		AntsAddAppointmentResponsePOJO responseObject =
766 				mapper.readValue( response, AntsAddAppointmentResponsePOJO.class );
767 
768 		// Check the result from the Object
769 		return responseObject.isSuccess( );
770 	}
771 
772 	/**
773 	 * Check whether an HTTP response returned a positive result (number of appointments deleted > 0)
774 	 * or not (0), when trying to delete an appointment with the ANTS API
775 	 * 
776 	 * @param response
777 	 * 				The content of the HTTP response returned by the ANTS API
778 	 * @return
779 	 * 				true if the appointment was deleted successfully, returns false otherwise
780 	 * @throws IOException
781 	 */
782 	public static boolean isAppointmentDeletionSuccessful( String response ) throws IOException
783 	{
784 		ObjectMapper mapper = new ObjectMapper( );
785 
786 		// Convert the content of the response into an Object
787 		AntsDeleteAppointmentResponsePOJO responseObject =
788 				mapper.readValue( response, AntsDeleteAppointmentResponsePOJO.class );
789 
790 		/*
791 		 * Check the Response:
792 		 * If rowcount == 0, then no appointment was deleted
793 		 * If rowcount > 0, then 1 or more appointments were successfully deleted
794 		 */
795 		if ( responseObject.getRowcount( ) > 0 )
796 		{
797 		    // The appointment was successfully deleted
798 		    return true;
799 		}
800 		else
801 		{
802 		    // The appointment was not deleted
803 		    AppLogService.error( "{} - The ANTS appointment was not deleted. HTTP response: '{}'", BEAN_SERVICE, response );
804 		    return false;
805 		}
806 	}
807 
808 	/**
809 	 * Get the ANTS application numbers value tied to an appointment
810 	 * 
811 	 * @param idAppointment
812 	 * 				ID of the appointment
813 	 * @param entryFieldId
814 	 * 				ID of the Entry used to save ANTS application numbers
815 	 * @return
816 	 * 				The ANTS application numbers value as a String
817 	 */
818 	public static String getAntsApplicationValuesFromResponse( int idAppointment, int entryFieldId )
819 	{
820 		List<Response> responseList = AppointmentResponseService.findListResponse( idAppointment );
821 
822 		for( Response response : responseList )
823 		{
824 			// If the response comes from an Entry that has the specified ID, then we retrieve its value
825 			if( response.getEntry( ).getIdEntry( ) == entryFieldId )
826 			{
827 				return response.getResponseValue( );
828 			}
829 		}
830 		return null;
831 	}
832 
833 	/**
834 	 * Split the values from a String with a specific separator
835 	 * 
836 	 * @param antsApplicationValues
837 	 * 				The String containing the values to split
838 	 * @param separator
839 	 * 				Character used to separate the different values
840 	 * @return
841 	 * 				A List containing the separated values
842 	 */
843 	public static List<String> splitAntsApplicationValues( String antsApplicationValues, String separator )
844 	{
845 		if( StringUtils.isNotBlank( antsApplicationValues ) )
846 		{
847 			String[] appNumbersArray = StringUtils.split( antsApplicationValues, separator );
848 			return Arrays.asList( appNumbersArray );
849 		}
850 		return Collections.emptyList( );
851 	}
852 
853 	/**
854 	 * Get the ID of the entry field where the ANTS application numbers of an appointment are saved
855 	 * 
856 	 * @param idTask
857 	 * 				ID of the task being executed
858 	 * 
859 	 * @return
860 	 * 				The ID of the entry field
861 	 */
862 	@Override
863 	public int getAntsApplicationFieldId( int idTask )
864 	{
865 		return _task_ants_appointment_dao.load( idTask ).getIdFieldEntry( );
866 	}
867 }