TaskAntsAppointmentService.java

/*
 * Copyright (c) 2002-2023, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.workflow.modules.appointmentants.service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
import fr.paris.lutece.plugins.appointment.business.localization.Localization;
import fr.paris.lutece.plugins.appointment.service.AppointmentResponseService;
import fr.paris.lutece.plugins.appointment.service.AppointmentService;
import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
import fr.paris.lutece.plugins.appointment.service.LocalizationService;
import fr.paris.lutece.plugins.appointment.web.AppointmentApp;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
import fr.paris.lutece.plugins.genericattributes.business.Response;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.business.TaskAntsAppointmentConfigDAO;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.business.history.TaskAntsAppointmentHistory;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsAddAppointmentResponsePOJO;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsDeleteAppointmentResponsePOJO;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.pojo.AntsStatusResponsePOJO;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.service.rest.TaskAntsAppointmentRest;
import fr.paris.lutece.plugins.workflow.modules.appointmentants.service.rest.TaskAntsAppointmentRestConstants;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.httpaccess.HttpAccessException;
import fr.paris.lutece.util.url.UrlItem;

/**
 * 
 * Class containing useful methods to handle ANTS related tasks
 * 
 */
public class TaskAntsAppointmentService implements ITaskAntsAppointmentService {

	public static final String BEAN_SERVICE = WorkflowAppointmentAntsPlugin.PLUGIN_NAME + ".taskAntsAppointmentService";

	@Inject
	@Named( TaskAntsAppointmentConfigDAO.BEAN_NAME )
	private TaskAntsAppointmentConfigDAO _task_ants_appointment_dao;	

	/**
	 * ANTS' API URLs
	 */
	private static final String ANTS_BASE_URL =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL );
	private static final String ANTS_STATUS_URL =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_STATUS_APPOINTMENT );

	/**
	 * ANTS' API URL Parameters
	 */
	private static final String URL_PARAMETER_APPLICATION_ID =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPLICATION_ID );
	private static final String URL_PARAMETER_APPLICATION_IDS =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPLICATION_IDS );
	private static final String URL_PARAMETER_MANAGEMENT_URL =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_MANAGEMENT_URL );
	private static final String URL_PARAMETER_MEETING_POINT =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_MEETING_POINT );
	private static final String URL_PARAMETER_APPOINTMENT_DATE =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPOINTMENT_DATE );

	/**
	 * Value of the ANTS API Token
	 */
	private static final String PROPERTY_API_OPT_AUTH_TOKEN_VALUE =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_TOKEN_VALUE );

	/**
	 * Status value of an ANTS appointment ("validated", "consumed", etc.)
	 */
	private static final String STATUS_VALIDATED =
			AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_APPOINTMENT_VALIDATED );

	/**
	 * Variables for general use
	 */
	private static final String APPLICATION_NUMBERS_SEPARATOR =
			AppPropertiesService.getProperty( "ants.api.application.numbers.separator" );

	private static final String PARIS_USER_ACCOUNT_URL =
			AppPropertiesService.getProperty( "paris.user.account.url" );

	/**
	 * Variables used to save / retrieve specific details of an appointment
	 */
	public static final String KEY_URL = "url";
	public static final String KEY_LOCATION = "location";
	public static final String KEY_DATE = "date";

	private TaskAntsAppointmentService( )
	{
	}

	/**
	 * Create an appointment in the ANTS database
	 * 
	 * @param request
	 * 				request to use
	 * @param idAppointment
	 * 				ID of the appointment that will be processed
	 * @param idTask
	 * 				ID of the workflow task calling this method
	 * @param antsAppointmentHistory
	 * 				Instance of TaskAntsAppointmentHistory object used to save the task's history
	 * @return
	 * 				true if it was successfully created, returns false if it failed
	 */
	@Override
	public boolean createAntsAppointment( HttpServletRequest request, int idAppointment, int idTask, TaskAntsAppointmentHistory antsAppointmentHistory )
	{
		Map<String, String> applicationContent = getAppointmentData( request, idAppointment, false );

		boolean isAppointmentCreated = false;

		// Get the ANTS application numbers from the appointment's Responses
		String strAntsApplicationNumbers = getAntsApplicationValuesFromResponse(
				idAppointment,
				getAntsApplicationFieldId( idTask )
				);

		// Split the potential ANTS application values retrieved from the appointment's Responses
		List<String> applicationNumberList = splitAntsApplicationValues( strAntsApplicationNumbers, APPLICATION_NUMBERS_SEPARATOR );

		// If the appointment has no application number(s), then stop the task
		if( CollectionUtils.isEmpty( applicationNumberList ) )
		{
			AppLogService.info( "{} - Appointment with ID {} has no ANTS number", BEAN_SERVICE, idAppointment );
			// Return true so the task stops with a positive result
			return true;
		}

		// Set the ANTS application numbers in the task's history
		antsAppointmentHistory.setAntsApplicationNumbers( strAntsApplicationNumbers );

		// Check if the application number used are valid and allow appointments creation
		if( isApplicationNumberListValidForCreation( idAppointment, applicationNumberList ) ) {

			// For each application number available, create a new ANTS appointment
			for( String appplicationNumber : applicationNumberList ) {

				// Build the ANTS URL used to create a new appointment
				String antsURL = buildAntsAddAppointmentUrl(
						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL),
						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_ADD_APPOINTMENT),
						appplicationNumber,
						applicationContent.get( KEY_URL ),
						applicationContent.get( KEY_LOCATION ),
						applicationContent.get( KEY_DATE )
						);
				try {
					// Create the appointment on the ANTS database
					isAppointmentCreated = addAntsAppointmentRestCall( antsURL );

					AppLogService.debug(
							"{} ANTS appointment with number '{}' was {} for appointment with ID {}",
							BEAN_SERVICE,
							appplicationNumber,
							isAppointmentCreated ? "created" : "not created",
							idAppointment
							);

					// If the appointment was not created
					if( !isAppointmentCreated )
					{
						return isAppointmentCreated;
					}
				}
				catch( Exception e )
				{
					AppLogService.error( BEAN_SERVICE, e );
				}
			}
		}
		return isAppointmentCreated;
	}

	/**
	 * Remove an appointment from the ANTS database
	 * 
	 * @param request
	 * 				request to use
	 * @param idAppointment
	 * 				ID of the appointment that will be processed
	 * @param idTask
	 * 				ID of the workflow task calling this method
	 * @param antsAppointmentHistory
	 * 				Instance of TaskAntsAppointmentHistory object used to save the task's history
	 * @return
	 * 				true if it was successfully deleted, returns false if it failed
	 */
	@Override
	public boolean deleteAntsAppointment( HttpServletRequest request, int idAppointment, int idTask, TaskAntsAppointmentHistory antsAppointmentHistory )
	{
		Map<String, String> applicationContent = getAppointmentData( request, idAppointment, true );

		boolean isAppointmentDeleted = false;

		// Get the ANTS application numbers from the appointment's Responses
		String strAntsApplicationNumbers = getAntsApplicationValuesFromResponse(
				idAppointment,
				getAntsApplicationFieldId( idTask )
				);

		// Split the potential ANTS application values retrieved from the appointment's Responses
		List<String> applicationNumberList = splitAntsApplicationValues( strAntsApplicationNumbers, APPLICATION_NUMBERS_SEPARATOR );

		// If the appointment has no application number(s), then stop the task
		if( CollectionUtils.isEmpty( applicationNumberList ) )
		{
			AppLogService.info( "{} - Appointment with ID {} has no ANTS number", BEAN_SERVICE, idAppointment );
			// Return true so the task stops with a positive result
			return true;
		}

		// Set the ANTS application numbers in the task's history
		antsAppointmentHistory.setAntsApplicationNumbers( strAntsApplicationNumbers );

		// Check if the application numbers used are valid and still allow the appointments to be deleted
		if( isApplicationNumberListValidForDeletion( idAppointment, applicationNumberList ) ) {
			// For each application number available, delete any existing ANTS appointment
			for( String appplicationNumber : applicationNumberList ) {

				// Build the ANTS URL used to delete an appointment
				String antsURL = buildAntsDeleteAppointmentUrl(
						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL),
						AppPropertiesService.getProperty( TaskAntsAppointmentRestConstants.ANTS_URL_DELETE_APPOINTMENT),
						appplicationNumber,
						applicationContent.get( KEY_LOCATION ),
						applicationContent.get( KEY_DATE )
						);
				try {
					// Delete the appointment from the ANTS database
					isAppointmentDeleted = deleteAntsAppointmentRestCall( antsURL );

					AppLogService.debug(
							"{} ANTS appointment with number '{}' was {} for appointment with ID {}",
							BEAN_SERVICE,
							appplicationNumber,
							isAppointmentDeleted ? "deleted" : "not deleted",
							idAppointment
							);

					// If the appointment was not deleted successfully
					if( !isAppointmentDeleted )
					{
						return isAppointmentDeleted;
					}
				}
				catch ( Exception e )
				{
					AppLogService.error( BEAN_SERVICE, e );
				}
			}
		}
		return isAppointmentDeleted;
	}

	/**
	 * Check if an appointment was created from the front office or from the back office
	 * 
	 * @param appointment
	 * 				The appointment to check
	 * @return
	 * 				true if it was created by a user in the front office, returns false otherwise
	 */
	public static boolean isAppointmentCreatedInFrontOffice( Appointment appointment )
	{
		/* If the appointment's "AdminUserCreate" field has no value, then it is considered
		 * that it was created by a user in the front office 
		 * */
		return appointment.getAdminUserCreate( ) == null ||
				appointment.getAdminUserCreate( ).isEmpty( );
	}

	/**
	 * Build the URL used to add an appointment in the ANTS DB
	 * 
	 * @param baseUrl
	 * 				The base URL of the ANTS API
	 * @param addAppointmentUrl
	 * 				The ANTS API's endpoint used to add appointments
	 * @param applicationId
	 * 				The ANTS application number used to create the appointment
	 * @param managementUrl
	 * 				The URL used to access the appointment's web page
	 * @param meetingPoint
	 * 				The location of the appointment
	 * @param dateTime
	 * 				The date and time of the appointment
	 * @return
	 * 				The complete URL used to create this specific appointment in
	 * 				the ANTS database
	 */
	public static String buildAntsAddAppointmentUrl( String baseUrl, String addAppointmentUrl, String applicationId,
			String managementUrl, String meetingPoint, String dateTime )
	{
		StringBuilder antsApiUrl =  new StringBuilder( baseUrl ).
				append( addAppointmentUrl );

		UrlItem urlItem = new UrlItem( antsApiUrl.toString( ) );
		urlItem.addParameter(URL_PARAMETER_APPLICATION_ID, applicationId );
		urlItem.addParameter(URL_PARAMETER_MANAGEMENT_URL, managementUrl );
		urlItem.addParameter(URL_PARAMETER_MEETING_POINT, meetingPoint );
		urlItem.addParameter(URL_PARAMETER_APPOINTMENT_DATE, dateTime );

		return urlItem.getUrl( );
	}

	/**
	 * Use the ANTS API to add an appointment to their database
	 * 
	 * @param antsUrl
	 * 				URL used to make the REST call
	 * @return
	 * 				true if the appointment was added successfully, returns false otherwise
	 * @throws HttpAccessException
	 * @throws IOException
	 */
	public static boolean addAntsAppointmentRestCall( String antsUrl ) throws HttpAccessException, IOException
	{
		String response = TaskAntsAppointmentRest.addAntsAppointment( antsUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );

		return isAppointmentCreationSuccessful( response );
	}

	/**
	 * Build the URL used to delete an appointment from the ANTS DB
	 * 
	 * @param baseUrl
	 * 				The base URL of the ANTS API
	 * @param deleteAppointmentUrl
	 * 				The ANTS API's endpoint used to delete an appointment
	 * @param applicationId
	 * 				The ANTS application number used to identify the
	 * 				appointment to delete
	 * @param meetingPoint
	 * 				The location of the appointment
	 * @param dateTime
	 * 				The date and time of the appointment
	 * @return
	 * 				The complete URL used to delete this specific appointment
	 * 				from the ANTS database
	 */
	public static String buildAntsDeleteAppointmentUrl( String baseUrl, String deleteAppointmentUrl, String applicationId,
			String meetingPoint, String dateTime )
	{
		StringBuilder antsApiUrl =  new StringBuilder( baseUrl ).
				append( deleteAppointmentUrl );

		UrlItem urlItem = new UrlItem( antsApiUrl.toString( ) );
		urlItem.addParameter(URL_PARAMETER_APPLICATION_ID, applicationId );
		urlItem.addParameter(URL_PARAMETER_MEETING_POINT, meetingPoint );
		urlItem.addParameter(URL_PARAMETER_APPOINTMENT_DATE, dateTime );

		return urlItem.getUrl( );
	}

	/**
	 * Use the ANTS API to delete an appointment from their database
	 * 
	 * @param antsUrl
	 * 				URL used to make the REST call
	 * @return
	 * 				true if the appointment was deleted successfully, returns false otherwise
	 * @throws HttpAccessException
	 * @throws IOException
	 */
	public static boolean deleteAntsAppointmentRestCall( String antsUrl ) throws HttpAccessException, IOException
	{
		String response = TaskAntsAppointmentRest.deleteAntsAppointment( antsUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );

		return isAppointmentDeletionSuccessful( response );
	}

	/**
	 * Retrieve the details of the current appointment (user name, email, date, etc.)
	 * 
	 * @param request
	 * 				The request from the current context
	 * @param idAppointment
	 * 				The ID of the appointment to process
	 * @param isDeletingAntsAppointment
	 * 				Whether the appointment is getting deleted (true) or if it is being created (false)
	 * @return
	 * 				A <Key, Value> list of the current appointment's URL,
	 * 				location and date
	 */
	public static Map<String, String> getAppointmentData( HttpServletRequest request, int idAppointment, boolean isDeletingAppointment )
	{
		Map<String, String> appointmentDataMap = new HashMap<>( );
		AppointmentDTO appointmentDto = null;

		// Check if the current appointment is being deleted
		if( isDeletingAppointment )
		{
			// Get the appointement's previous data, in case it is being rescheduled
			appointmentDto = getOldAppointment( request );

			if( appointmentDto == null )
			{
				// The appointment isn't being rescheduled, so we retrieve its current data
				appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( idAppointment );
			}
		}
		else
		{
			// The appointment is being created, so we retrieve its data
			appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( idAppointment );
		}

		// Get the URL of the user's account on PARIS' web site, and encode it
		appointmentDataMap.put(
				KEY_URL,
				cleanUrl( PARIS_USER_ACCOUNT_URL ) );

		// Get the appointment's location
		String appointmentLocation = "";

		if( appointmentDto != null ) {
			Localization localization = LocalizationService.findLocalizationWithFormId( appointmentDto.getIdForm( ) );
			if( localization != null && localization.getAddress( ) != null )
			{
				appointmentLocation = localization.getAddress( );
			}
		}
		appointmentDataMap.put(
				KEY_LOCATION,
				cleanUrl( appointmentLocation ).replace( "+" , "%20" ) );

		// Get the appointment's date and time
		String appointmentDateTime = "";

		if( appointmentDto != null )
		{
			String startingDateTime = appointmentDto.getStartingDateTime( ).toString( );

			// Encode the date and time so they fit properly in a URL and encode the ':' characters
			appointmentDateTime = cleanUrl( startingDateTime ).replace( "+" , "%20" );
		}
		appointmentDataMap.put(
				KEY_DATE,
				appointmentDateTime );

		return appointmentDataMap;
	}

	/**
	 * Get the AppointmentDTO containing the previous data of an appointment. It is retrieved
	 * from the request's attributes
	 * 
	 * @param request
	 * 				The request containing the AppointmentDTO in its attributes
	 * @return
	 * 				The AppointmentDTO Object containing the previous data if it was found,
	 * 				returns null otherwise
	 */
	private static AppointmentDTO getOldAppointment( HttpServletRequest request )
	{
		AppointmentDTO oldAppointment = null;

		try
		{
			// Retrieve the previous appointment from the request's parameters
			oldAppointment = ( AppointmentDTO ) request.getAttribute( AppointmentUtilities.OLD_APPOINTMENT_DTO );
		}
		catch ( Exception e )
		{
			AppLogService.info( BEAN_SERVICE + " removing appointment from ants database: {}", e.getMessage( ) );
		}
		return oldAppointment;
	}

	/**
	 * Get the status of every ANTS application numbers given as parameter
	 * 
	 * @param applicationNumberList
	 * 				List of the application numbers for which the status
	 * 				will be retrieved
	 * @return
	 * 				A list of Objects representing the status of the given ANTS
	 * 				application	numbers, returns an empty List if no element was found
	 * @throws HttpAccessException
	 */
	public static List<AntsStatusResponsePOJO> getAntsStatusResponseAsObjects( List<String> applicationNumberList )
			throws HttpAccessException
	{
		String getStatusUrl = buildAntsGetStatusAppointmentUrl( applicationNumberList );

		String response = "";

		List<AntsStatusResponsePOJO> statusObjectsList = new ArrayList<>( );

		response = TaskAntsAppointmentRest.getAntsAppointmentStatus( getStatusUrl, PROPERTY_API_OPT_AUTH_TOKEN_VALUE );
		AppLogService.debug( "{} - ANTS GET STATUS request successful - Response: {}", BEAN_SERVICE, response );

		// If the HTTP call was made and returned a response
		if( StringUtils.isNotBlank( response ) )
		{
			try
			{
				statusObjectsList = getStatusResponseAsObject( response );
			}
			catch( IOException e)
			{
				AppLogService.error( BEAN_SERVICE, e );
			}
		}
		return statusObjectsList;
	}

	/**
	 * Build the URL used to get the status of specific ANTS appointments
	 * 
	 * @param applicationIdsList
	 * 				The List of ANTS application numbers to use
	 * @return
	 * 				The complete URL used to check the status of appointments
	 * 				from the ANTS database
	 */
	public static String buildAntsGetStatusAppointmentUrl( List<String> applicationIdsList )
	{
		// Build the base ANTS API URL used to retrieve the status of appointments
		StringBuilder antsApisUrl = new StringBuilder(
				ANTS_BASE_URL ).append( ANTS_STATUS_URL );

		UrlItem urlItem = new UrlItem( antsApisUrl.toString( ) );

		// Add every ANTS application number to the URL's parameters
		for( String applicationId : applicationIdsList )
		{
			urlItem.addParameter( URL_PARAMETER_APPLICATION_IDS, applicationId );
		}
		return urlItem.getUrl();
	}

	/**
	 * Check if the status of the given application numbers are valid and allow to add
	 * new appointments ('validated' status and empty list of appointments)
	 * 
	 * @param idAppointment
	 * 				ID of the appointment being processed
	 * @param applicationNumberList
	 * 				List of ANTS application numbers to check for validity
	 * @return
	 * 				true if all the application numbers are valid, false otherwise
	 */
	public static boolean isApplicationNumberListValidForCreation( int idAppointment, List<String> applicationNumberList ) 
	{
		List<AntsStatusResponsePOJO> statusResponseList = null;
		try
		{
			// Get the status of the given ANTS application number(s)
			statusResponseList = getAntsStatusResponseAsObjects( applicationNumberList );
		}
		catch ( Exception e )
		{
			AppLogService.error( BEAN_SERVICE, e );
			return false;
		}

		if( CollectionUtils.isEmpty( statusResponseList ) )
		{
			AppLogService.info( "{} - No status retrieved for the ANTS numbers {}",
					BEAN_SERVICE, Arrays.toString( applicationNumberList.toArray( ) ) );
			return false;
		}

		// Check the validity of each application number's status and appointments
		for( AntsStatusResponsePOJO statusResponse : statusResponseList )
		{
			String statusAntsNumber = statusResponse.getStatus( );
			Object[] listAntsNumberAppointments = statusResponse.getAppointments( );

			/* If the application number hasn't been validated, or if it already has
			 * appointments tied to it, then we shouldn't create any appointment
			 * */
			if( !StringUtils.equals( statusAntsNumber, STATUS_VALIDATED ) ||
					ArrayUtils.isNotEmpty( listAntsNumberAppointments ) )
			{
				AppLogService.error(
						"{} - ANTS appointment not valid for creation: Appointment {} with ANTS number '{}' has a status '{}' and {} appointment(s)",
						BEAN_SERVICE, idAppointment, Arrays.toString( applicationNumberList.toArray( ) ), statusAntsNumber, listAntsNumberAppointments.length );
				return false;
			}
		}
		return true;
	}

	/**
	 * Check if the status of the given application numbers are valid and allow to delete
	 * existing appointments ('validated' status and at least 1 element in their list of appointment)
	 * 
	 * @param idAppointment
	 * 				ID of the appointment being processed
	 * @param applicationNumberList
	 * 				List of ANTS application numbers to check for potential deletion
	 * @return
	 * 				true if the appointments with the given application numbers can be deleted,
	 * 				false otherwise
	 */
	public static boolean isApplicationNumberListValidForDeletion( int idAppointment, List<String> applicationNumberList )
	{
		List<AntsStatusResponsePOJO> statusResponseList = null;
		try
		{
			// Get the status of the given ANTS application number(s)
			statusResponseList = getAntsStatusResponseAsObjects( applicationNumberList );
		}
		catch ( Exception e )
		{
			AppLogService.error( BEAN_SERVICE, e );
			return false;
		}

		if( CollectionUtils.isEmpty( statusResponseList ) )
		{
			AppLogService.info( "{} - No status retrieved for the ANTS numbers {}",
					BEAN_SERVICE, Arrays.toString( applicationNumberList.toArray( ) ) );
			return false;
		}

		// Check the validity of each application number's status and appointments
		for( AntsStatusResponsePOJO statusResponse : statusResponseList )
		{
			String statusAntsNumber = statusResponse.getStatus( );
			Object[] listAntsNumberAppointments = statusResponse.getAppointments( );

			/* If the application number hasn't been validated, and if it has no
			 * appointment tied to it, then we can't delete it
			 * */
			if( !StringUtils.equals( statusAntsNumber, STATUS_VALIDATED ) ||
					ArrayUtils.isEmpty( listAntsNumberAppointments ) )
			{
				AppLogService.error(
						"{} - ANTS appointment not valid for deletion: Appointment {} with ANTS number '{}' has a status '{}' and no appointment",
						BEAN_SERVICE, idAppointment, Arrays.toString( applicationNumberList.toArray( ) ), statusAntsNumber );
				return false;
			}
		}
		return true;
	}

	/**
	 * Creates a List of {@link AntsStatusResponsePOJO} Objects from a json String containing the status
	 * and appointments list that were returned by the ANTS API
	 * 
	 * @param response
	 * 				The content of the HTTP response returned by the ANTS API after getting the status of 
	 * 				one or more ANTS application number(s)
	 * @return
	 * 				A List of AntsStatusResponsePOJO Objects. Each item represents the status of one ANTS
	 * 				application number
	 * @throws IOException
	 */
	public static List<AntsStatusResponsePOJO> getStatusResponseAsObject( String response ) throws IOException
	{
		ObjectMapper mapper = new ObjectMapper( );
		JsonNode jsonNode = mapper.readTree( response );

		List<AntsStatusResponsePOJO> statusList = new ArrayList<>( );

		Iterator<String> fieldNames = jsonNode.fieldNames();

		// Build Objects from all the application numbers available
		while( fieldNames.hasNext( ) )
		{
			String fieldName = fieldNames.next( );
			JsonNode field = jsonNode.get( fieldName );
			statusList.add(
					mapper.readerFor( AntsStatusResponsePOJO.class )
					.readValue( field.toString() )
					);
		}
		return statusList;
	}

	/**
	 * Properly encode a String containing a URL to make sure it has the proper format (special characters encoding...)
	 * 
	 * @param urlToClean
	 * 				The URL to process
	 * @return
	 * 				A URL with encoded characters. Returns the initial URL if an error occurred
	 */
	public static String cleanUrl( String urlToClean )
	{
		try
		{
			return URLEncoder.encode( urlToClean, "utf-8" );
		}
		catch( UnsupportedEncodingException e)
		{
			AppLogService.error( BEAN_SERVICE, e );
			return urlToClean;
		}
	}

	/**
	 * Check whether an HTTP response returned a successful result or not when
	 * trying to create an appointment with the ANTS API
	 * 
	 * @param response
	 * 				The content of the HTTP response returned by the ANTS API
	 * @return
	 * 				true if the appointment was created successfully, returns false otherwise
	 * @throws IOException
	 */
	public static boolean isAppointmentCreationSuccessful( String response ) throws IOException
	{
		ObjectMapper mapper = new ObjectMapper( );

		// Convert the content of the response into an Object
		AntsAddAppointmentResponsePOJO responseObject =
				mapper.readValue( response, AntsAddAppointmentResponsePOJO.class );

		// Check the result from the Object
		return responseObject.isSuccess( );
	}

	/**
	 * Check whether an HTTP response returned a positive result (number of appointments deleted > 0)
	 * or not (0), when trying to delete an appointment with the ANTS API
	 * 
	 * @param response
	 * 				The content of the HTTP response returned by the ANTS API
	 * @return
	 * 				true if the appointment was deleted successfully, returns false otherwise
	 * @throws IOException
	 */
	public static boolean isAppointmentDeletionSuccessful( String response ) throws IOException
	{
		ObjectMapper mapper = new ObjectMapper( );

		// Convert the content of the response into an Object
		AntsDeleteAppointmentResponsePOJO responseObject =
				mapper.readValue( response, AntsDeleteAppointmentResponsePOJO.class );

		/*
		 * Check the Response:
		 * If rowcount == 0, then no appointment was deleted
		 * If rowcount > 0, then 1 or more appointments were successfully deleted
		 */
		if ( responseObject.getRowcount( ) > 0 )
		{
		    // The appointment was successfully deleted
		    return true;
		}
		else
		{
		    // The appointment was not deleted
		    AppLogService.error( "{} - The ANTS appointment was not deleted. HTTP response: '{}'", BEAN_SERVICE, response );
		    return false;
		}
	}

	/**
	 * Get the ANTS application numbers value tied to an appointment
	 * 
	 * @param idAppointment
	 * 				ID of the appointment
	 * @param entryFieldId
	 * 				ID of the Entry used to save ANTS application numbers
	 * @return
	 * 				The ANTS application numbers value as a String
	 */
	public static String getAntsApplicationValuesFromResponse( int idAppointment, int entryFieldId )
	{
		List<Response> responseList = AppointmentResponseService.findListResponse( idAppointment );

		for( Response response : responseList )
		{
			// If the response comes from an Entry that has the specified ID, then we retrieve its value
			if( response.getEntry( ).getIdEntry( ) == entryFieldId )
			{
				return response.getResponseValue( );
			}
		}
		return null;
	}

	/**
	 * Split the values from a String with a specific separator
	 * 
	 * @param antsApplicationValues
	 * 				The String containing the values to split
	 * @param separator
	 * 				Character used to separate the different values
	 * @return
	 * 				A List containing the separated values
	 */
	public static List<String> splitAntsApplicationValues( String antsApplicationValues, String separator )
	{
		if( StringUtils.isNotBlank( antsApplicationValues ) )
		{
			String[] appNumbersArray = StringUtils.split( antsApplicationValues, separator );
			return Arrays.asList( appNumbersArray );
		}
		return Collections.emptyList( );
	}

	/**
	 * Get the ID of the entry field where the ANTS application numbers of an appointment are saved
	 * 
	 * @param idTask
	 * 				ID of the task being executed
	 * 
	 * @return
	 * 				The ID of the entry field
	 */
	@Override
	public int getAntsApplicationFieldId( int idTask )
	{
		return _task_ants_appointment_dao.load( idTask ).getIdFieldEntry( );
	}
}