package com.enactor.training.paymentService.service.actions.gcash;

import java.util.Map;

import com.enactor.commonUI.CommonUIDataTypes;
import com.enactor.commonUI.webService.rest.ContentTypeInferredResponseMessageHandler;
import com.enactor.core.application.process.ApplicationProcessDataType;
import com.enactor.core.application.process.ApplicationProcessException;
import com.enactor.core.application.process.ErrorDetails;
import com.enactor.core.application.process.IApplicationProcessData;
import com.enactor.core.application.process.IApplicationProcessDataType;
import com.enactor.core.application.process.IApplicationProcessOutcome;
import com.enactor.core.application.process.IErrorDetails;
import com.enactor.core.authentication.IIdentityTokenStore;
import com.enactor.core.authentication.OpenIdAuthenticationMessageHandler;
import com.enactor.core.endpoint.EndpointMonitor;
import com.enactor.core.entities.IEntity;
import com.enactor.core.json.rest.RestJsonInferredMessageHandler;
import com.enactor.core.json.rest.RestQNameJsonInferredMessageHandler;
import com.enactor.core.json.serializer.JsonSerializerUtil;
import com.enactor.core.processConnections.IEndpointReference;
import com.enactor.core.signOn.IUserPasswordPrincipal;
import com.enactor.core.signOn.IUserPrincipal;
import com.enactor.core.signOn.IUserPrincipleWithKeyStore;
import com.enactor.core.utilities.ClassFactory;
import com.enactor.core.utilities.Logger;
import com.enactor.core.utilities.StringUtils;
import com.enactor.core.utilities.WebServicesUtil;
import com.enactor.core.webService.epr.IRestEndpoint;
import com.enactor.core.webService.epr.RestPayloadFormat;
import com.enactor.core.webService.rest.DefaultRestServiceInvoker;
import com.enactor.core.webService.rest.IRestAuthenticationHandler;
import com.enactor.core.webService.rest.IRestMessageHandler;
import com.enactor.core.webService.rest.IRestRequestMessageHandler;
import com.enactor.core.webService.rest.IRestResponseMessageHandler;
import com.enactor.core.webService.rest.IRestServiceInvoker;
import com.enactor.core.webService.rest.OAuthOneLeggedAuthenticationHandler;
import com.enactor.core.webService.rest.RestBasicAuthenticationHandler;
import com.enactor.core.webService.rest.RestFileRequestMessageHandler;
import com.enactor.core.webService.rest.RestRawMessageHandler;
import com.enactor.core.webService.rest.RestServiceInvocationException;
import com.enactor.core.webService.rest.RestTextMessageHandler;
import com.enactor.core.webService.rest.RestXApiKeyAuthenticationHandler;
import com.enactor.core.webService.rest.RestXmlMessageHandler;
import com.enactor.core.webService.rest.RestXmlToJsonMessageHandler;
import com.enactor.coreUI.actions.IUIAction;
import com.enactor.coreUI.actions.UIActionFunctions;
import com.enactor.coreUI.annotations.Input;
import com.enactor.coreUI.annotations.Inputs;
import com.enactor.coreUI.annotations.Outcomes;
import com.enactor.coreUI.annotations.Output;
import com.enactor.coreUI.annotations.Outputs;
import com.enactor.coreUI.processConnections.actions.ResolveConnectedProcessEndpointReferenceAction;
import com.enactor.coreUI.processes.CoreUIDataTypes;
import com.enactor.coreUI.processes.CoreUIOutcomes;
import com.enactor.coreUI.processes.UIProcessContext;
import com.enactor.coreUI.processes.UIProcessException;

/**
 * An action that can invoke a rest webservice. It will examine the endpoint
 * (either supplied to resolved) to determine what authentication handler is
 * required. It can also examine the endpoint to determine which message handler
 * is needed, but this can be over-ridden by setting the MessageHandler classname
 * property.
 *
 * @Input ConnectedProcessId ID of the connected process to where the object is
 *        to be sent<br>
 *        Type: {@linkplain java.lang.String} (Optional)<br>
 *        Example: CashManagement
 *
 * @Input ConnectionPointId ID of the connection point <br>
 *        Type: {@linkplain java.lang.String} (Optional)<br>
 *        Example: Transactions
 *
 * @Input ConnectedProcessClientId ID of the connected process client to where
 *        the object is to be sent. This will default to ${Service.DeviceID} if
 *        not supplied<br>
 *        Type: {@linkplain java.lang.String} (Optional) <br>
 *        Example: enactor.store1.pos1
 *
 * @Input ConnectedProcessHostId ID of the connected process host to where the
 *        object is to be sent. This will default to ${Service.DeviceType} if
 *        not supplied<br>
 *        Type: {@linkplain java.lang.String} (Optional)<br>
 *        Example: POS
 *
 * @Input ProcessConnectionsDefinitionId ID of the process connections
 *        definition. If not supplied this will default to
 *        ${ProcessConnections.DefinitionId}<br>
 *        Type: {@linkplain java.lang.String} (Optional)<br>
 *        Example: StandardPosEstateManager
 *
 * @Input ProcessConnectionsContext Context for the process connections. If this
 *        parameter is not provided, the inputted client ID and host ID are used
 *        to determine the required value.<br>
 *        Type:
 *        {@linkplain com.enactor.core.processConnections.IProcessConnectionsContext}
 *        (Optional)
 *
 * @Input EndpointReference An optional pre-resolved endpoint. If this is supplied
 *        the previous parameters are not used to attempt to resolve the endpoint.<br>
 *        Type:
 *        {@linkplain com.enactor.core.processConnections.IProcessConnectionsContext}
 *        (Optional)
 *
 * @Input AuthenticationHandler The class name of an alternative authentication
 * 		  handler to use when invoking the service. If not supplied the action
 *        will attempt to infer the handler to use from the endpoint.
 *        Type: {@linkplain java.lang.String}
 *        (Optional)
 *
 * @Input MessageHandler The class name of an alternative message handler to use
 *        when invoking the service. If not supplied the action will attempt
 *        to infer the handler to use from the endpoint and request object.
 *        Type: {@linkplain java.lang.String}
 *        (Optional)
 *
 * @Input RequestMessageHandler The class name of an alternative request message handler to use
 *        when invoking the service. If not supplied the action will attempt
 *        to infer the handler to use from the endpoint.
 *        Type: {@linkplain java.lang.String}
 *        (Optional)
 *
 * @Input ResponseMessageHandler The class name of an alternative response message handler to use
 *        when invoking the service. If not supplied the action will attempt
 *        to infer the handler to use from the endpoint and request object.
 *        Type: {@linkplain java.lang.String}
 *        (Optional)
 *
 * @Input ResponseObjectClassName The class name of object that we are expecting to be returned in the
 *        REST response. Must be supplied in the case where the payload format of the rest endpoint is
 *        JSON since the Jackson processor needs to the know the class of the object that it is converting
 *        from JSON into.
 *        Type: {@linkplain java.lang.String}
 *        (Optional)
 *
 * @Input JsonSerialiserFormat The format that will be used for serialization and deserialization.  If
 *        the communication is going to be with other Enactor systems then the format should probably be
 *        ENACTOR which will cause all Json to use default typing, which means type information (i.e. class
 *        names) will be included in the Json data. If the communication is going to be systems that cannot
 *        process this type information then the format should be SIMPLE in which case type information is
 *        not included.  Defaults to ENACTOR.
 *        Type: {@linkplain com.enactor.core.json.serializer.JsonSerializerUtil$Format}
 *        (Optional)
 *
 * @Input TrainingMode Flag to indicate whether the training mode is enabled or not.
 * 		  This flag is considered when resolving the endPoint reference.
 *        If there is a specific endpoint is defined for training mode use that one or else use the default.
 *
 * @Input Object The object to send to the service. Based on the format supported
 *        by the service (configured in the endpoint) and/or supported by the
 *        message handler, this may be required to be of a specific type. *
 *        Type: {@linkplain java.lang.Object}
 *
 * @Output Object The response from the service. This will have been converted
 *         from either a JSON or XML stream into a Java object based on the
 *         format supported by the service (configured in the endpoint)<br>
 *         Type: {@linkplain java.lang.Object}
 *
 * @Output StatusCode The response status code received. This data is set only if the service
 *         invocation was failed
 *         Type: {@linkplain java.lang.Integer}
 *
 * @Outcome Success Message has been sent
 *
 * @Exception ProcessException Exception with an appropriate message set
 *
 * @Categories message, process
 *
 * @Keywords message, xml, queue, process, web service
 *
 * @ResourceType ACTION
 *
 * @author leesmith
 */
@Inputs( {
	@Input(name = CoreUIDataTypes.CONNECTED_PROCESS_CLIENT_ID_DATA_NAME, type = java.lang.String.class),
	@Input(name = CoreUIDataTypes.CONNECTED_PROCESS_HOST_ID_DATA_NAME, type = java.lang.String.class),
	@Input(name = CoreUIDataTypes.CONNECTED_PROCESS_ID_DATA_NAME, type = java.lang.String.class),
	@Input(name = CoreUIDataTypes.CONNECTION_POINT_ID_DATA_NAME, type = java.lang.String.class),
	@Input(name = CoreUIDataTypes.PROCESS_CONNECTIONS_CONTEXT_DATA_NAME, type = com.enactor.core.processConnections.IProcessConnectionsContext.class),
	@Input(name = CoreUIDataTypes.PROCESS_CONNECTIONS_DEFINITION_ID_DATA_NAME, type = java.lang.String.class),
	@Input(name = CoreUIDataTypes.ENDPOINT_REFERENCE_DATA_NAME, type = IEndpointReference.class),
	@Input(name = InvokeRestServiceAction.AUTHENTICATION_HANDLER_DATA_NAME, type = String.class),
	@Input(name = InvokeRestServiceAction.MESSAGE_HANDLER_DATA_NAME, type = String.class),
	@Input(name = CoreUIDataTypes.OBJECT_DATA_NAME, type = java.lang.Object.class),
		@Input(name = InvokeRestServiceAction.PARAMETERS_DATA_NAME, type = Map.class),
	@Input(name = InvokeRestServiceAction.RESPONSE_OBJECT_CLASS_NAME_DATA_NAME, type = String.class),
	@Input(name = InvokeRestServiceAction.JSON_SERIALIZER_FORMAT_DATA_NAME, type = com.enactor.core.json.serializer.JsonSerializerUtil.Format.class),
	@Input(name = CoreUIDataTypes.TRAINING_MODE_DATA_NAME, type = Boolean.class),
	@Input(name = InvokeRestServiceAction.HEADERS_DATA_NAME, type = Map.class),
	@Input(name = InvokeRestServiceAction.REQUEST_TRANSFORM_PARAMS_DATA_NAME, type = Map.class),
	@Input(name = InvokeRestServiceAction.RESPONSE_TRANSFORM_PARAMS_DATA_NAME,
				type = Map.class),
	@Input(name = CommonUIDataTypes.LOCATION_ID_NAME, type = String.class),
	@Input(name = CoreUIDataTypes.CONNECT_TIMEOUT_SECS_NAME, type = Integer.class),
	@Input(name = CoreUIDataTypes.SOCKET_TIMEOUT_SECS_NAME, type = Integer.class)
})
@Outputs({
	@Output(name = CoreUIDataTypes.OBJECT_DATA_NAME, type = Object.class),
	@Output(name = ErrorDetails.ENTITY_DATA_TYPE_NAME, type = ErrorDetails.class),
	@Output(name = InvokeRestServiceAction.STATUS_CODE_DATA_NAME, type = Integer.class)
})
@Outcomes( {
	CoreUIOutcomes.FAIL_OUTCOME_NAME,
	CoreUIOutcomes.SUCCESS_OUTCOME_NAME
})
public class InvokeRestServiceAction  implements IUIAction {
	/** Default serial version uid */
	private static final long serialVersionUID = 1L;

	/** My logger */
	private static final Logger logger = Logger.getLogger(InvokeRestServiceAction.class);

	/** The classname of an alternative authentication handler to use */
	public static final String AUTHENTICATION_HANDLER_DATA_NAME = "enactor.service.AuthenticationHandler";
	public static final IApplicationProcessDataType AUTHENTICATION_HANDLER_DATA =
			new ApplicationProcessDataType(AUTHENTICATION_HANDLER_DATA_NAME, String.class.getName());

	/** The classname of an alternative message handler to use */
	public static final String MESSAGE_HANDLER_DATA_NAME = "enactor.service.MessageHandler";
	public static final IApplicationProcessDataType MESSAGE_HANDLER_DATA =
			new ApplicationProcessDataType(MESSAGE_HANDLER_DATA_NAME, String.class.getName());

	/** The classname of an alternative request message handler to use */
	public static final String REQUEST_MESSAGE_HANDLER_DATA_NAME = "enactor.service.RequestMessageHandler";
	public static final IApplicationProcessDataType REQUEST_MESSAGE_HANDLER_DATA =
			new ApplicationProcessDataType(REQUEST_MESSAGE_HANDLER_DATA_NAME, String.class.getName());

	/** The classname of an alternative response message handler to use */
	public static final String RESPONSE_MESSAGE_HANDLER_DATA_NAME = "enactor.service.ResponseMessageHandler";
	public static final IApplicationProcessDataType RESPONSE_MESSAGE_HANDLER_DATA =
			new ApplicationProcessDataType(RESPONSE_MESSAGE_HANDLER_DATA_NAME, String.class.getName());

	/** The parameters to be sent */
	public static final String PARAMETERS_DATA_NAME = "enactor.service.Parameters";
	public static final IApplicationProcessDataType PARAMETERS_DATA = new ApplicationProcessDataType(
			PARAMETERS_DATA_NAME, Map.class.getName());

	/** The request transform parameters */
	public static final String REQUEST_TRANSFORM_PARAMS_DATA_NAME = "enactor.webService.RequestTransformParams";
	public static final IApplicationProcessDataType REQUEST_TRANSFORM_PARAMS_DATA = new ApplicationProcessDataType(
			REQUEST_TRANSFORM_PARAMS_DATA_NAME, Map.class.getName());

	/** The response transform parameters */
	public static final String RESPONSE_TRANSFORM_PARAMS_DATA_NAME = "enactor.webService.ResponseTransformParams";
	public static final IApplicationProcessDataType RESPONSE_TRANSFORM_PARAMS_DATA = new ApplicationProcessDataType(
			RESPONSE_TRANSFORM_PARAMS_DATA_NAME, Map.class.getName());

	/** The class name of the object that we are expecting to be returned by the service call */
	public static final String RESPONSE_OBJECT_CLASS_NAME_DATA_NAME = "enactor.service.ResponseObjectClassName";
	public static final IApplicationProcessDataType RESPONSE_OBJECT_CLASS_NAME_DATA =
			new ApplicationProcessDataType(RESPONSE_OBJECT_CLASS_NAME_DATA_NAME, String.class.getName());

	/** The class name of the object that we are expecting to be returned by the service call in case of an error response */
	public static final String RESPONSE_ERROR_CLASS_NAME_DATA_NAME = "enactor.service.ResponseErrorClassName";
	public static final IApplicationProcessDataType RESPONSE_ERROR_CLASS_NAME_DATA =
			new ApplicationProcessDataType(RESPONSE_ERROR_CLASS_NAME_DATA_NAME, String.class.getName());

	/** The Json serializer format that should be used in the service call */
	public static final String JSON_SERIALIZER_FORMAT_DATA_NAME = "enactor.service.JsonSerializerFormat";
	public static final IApplicationProcessDataType JSON_SERIALIZER_FORMAT_DATA =
			new ApplicationProcessDataType(JSON_SERIALIZER_FORMAT_DATA_NAME, String.class.getName());

	/** The headers to be send */
	public static final String HEADERS_DATA_NAME = "enactor.service.Headers";
	public static final IApplicationProcessDataType HEADERS_DATA =
			new ApplicationProcessDataType(HEADERS_DATA_NAME, Map.class.getName());

	/** The headers to be returned */
	public static final String RESPONSE_HEADERS_DATA_NAME = "enactor.service.ResponseHeaders";
	public static final IApplicationProcessDataType RESPONSE_HEADERS_DATA = new ApplicationProcessDataType(
			RESPONSE_HEADERS_DATA_NAME, Map.class.getName());

	/** The response status code received */
	public static final String STATUS_CODE_DATA_NAME = "enactor.service.StatusCode";
	public static final IApplicationProcessDataType STATUS_CODE_DATA =
			new ApplicationProcessDataType(STATUS_CODE_DATA_NAME, Integer.class.getName());

	public static final String IDENTITY_TOKEN_STORE_CLASS_DATA_NAME = "enactor.service.IdentityTokenStoreClassName";
	public static final IApplicationProcessDataType IDENTITY_TOKEN_STORE_DATA_CLASS_NAME_TYPE = new ApplicationProcessDataType(IDENTITY_TOKEN_STORE_CLASS_DATA_NAME, String.class);
	public static final String IDENTITY_TOKEN_STORE_CLASS_NAME = "com.enactor.commonUI.authentication.IdentityTokenStore";

	@Override
	public IApplicationProcessOutcome execute(
			IApplicationProcessData inputData,
			IApplicationProcessData outputData) throws UIProcessException,
			ApplicationProcessException {
		// The request to send to the service (if any)
		Object requestObject = UIActionFunctions.getOptionalDataItem(inputData, CoreUIDataTypes.OBJECT_DATA);
        Integer connectTimeoutSecs = UIActionFunctions.getOptionalDataItem(inputData, CoreUIDataTypes.CONNECT_TIMEOUT_SECS_NAME);
		Integer socketTimeoutSecs = UIActionFunctions.getOptionalDataItem(inputData, CoreUIDataTypes.SOCKET_TIMEOUT_SECS_NAME);
		
		// Get the endpoint reference...
		IEndpointReference endpoint = (IEndpointReference) UIActionFunctions.getOptionalDataItem(inputData,
				CoreUIDataTypes.ENDPOINT_REFERENCE_DATA);
		if (endpoint == null) {
			// None was supplied, so try and resolve one.
			ResolveConnectedProcessEndpointReferenceAction resolveEndpointAction =
					new ResolveConnectedProcessEndpointReferenceAction();
                        resolveEndpointAction.setView(UIProcessContext.getContext().getProcessRunner().getView());
			IApplicationProcessOutcome outcome = resolveEndpointAction.execute(inputData, outputData);
			if (!outcome.equals(CoreUIOutcomes.SUCCESS_OUTCOME)) {
				return outcome;
			}

			endpoint = (IEndpointReference) outputData.getData(CoreUIDataTypes.ENDPOINT_REFERENCE_DATA);
		}

		if (endpoint == null) {
			// No endpoint, can't continue
			throw new UIProcessException(UIProcessException.CONFIGURATION_ERROR,
					"No endpoint was supplied, or could be resolved from the supplied inputs");
		}
		if (!(endpoint instanceof IRestEndpoint)) {
			// Wrong endpoint type
			throw new UIProcessException(UIProcessException.CONFIGURATION_ERROR,
					"Wrong endpoint type: " + endpoint.getClass().getName());
		}

		IRestEndpoint restEndpoint = (IRestEndpoint) endpoint;
		IRestServiceInvoker invoker = new DefaultRestServiceInvoker();

		// Populate any provided request/response transform parameters
		Map<String, String> requestTransformParams = UIActionFunctions
				.getOptionalDataItem(inputData, REQUEST_TRANSFORM_PARAMS_DATA);
		Map<String, String> responseTransformParams = UIActionFunctions
				.getOptionalDataItem(inputData, RESPONSE_TRANSFORM_PARAMS_DATA);
		if (requestTransformParams != null && !requestTransformParams.isEmpty()) {
			for (Map.Entry<String, String> entry : requestTransformParams.entrySet()) {
				restEndpoint.getRequestTransformerParams().put(entry.getKey(), entry.getValue());
			}
		}
		if (responseTransformParams != null && !responseTransformParams.isEmpty()) {
			for (Map.Entry<String, String> entry : responseTransformParams.entrySet()) {
				restEndpoint.getResponseTransformerParams().put(entry.getKey(), entry.getValue());
			}
		}

		// Check the authentication type, and set the correct authentication handler
		String authenticationHandler = (String) UIActionFunctions.getOptionalDataItem(inputData, AUTHENTICATION_HANDLER_DATA);
		if (authenticationHandler != null) {
			try {
				IRestAuthenticationHandler handler = (IRestAuthenticationHandler) ClassFactory.createInstance(authenticationHandler);
				invoker.setAuthenticationHandler(handler);
			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
				logger.log(Logger.LOG_ERROR, "Failed to invoke REST service", e);

				return CoreUIOutcomes.FAIL_OUTCOME;
			}
		} else if (restEndpoint.getAuthenticationType() != null) {
			switch (restEndpoint.getAuthenticationType()) {
			case NONE:
				break;

			case OAUTH_VERSION_1_OneLegged:
				invoker.setAuthenticationHandler(new OAuthOneLeggedAuthenticationHandler());
				break;

			case BASIC:
				invoker.setAuthenticationHandler(new RestBasicAuthenticationHandler());

				String locationId = UIActionFunctions.getOptionalDataItem(inputData,
						CommonUIDataTypes.LOCATION_ID);
				IUserPrincipal userPrincipal = WebServicesUtil.getUserPrincipalForEPR(restEndpoint,
						"", locationId);
				if (userPrincipal != null) {
					restEndpoint.setProperty(RestBasicAuthenticationHandler.AUTHENTICATION_USERNAME,
							userPrincipal.getUserId());
					if (userPrincipal instanceof IUserPasswordPrincipal) {
						IUserPasswordPrincipal passwordPrincipal = (IUserPasswordPrincipal) userPrincipal;
						restEndpoint.setProperty(
								RestBasicAuthenticationHandler.AUTHENTICATION_PASSWORD,
								passwordPrincipal.getPassword());
					}
					if (userPrincipal instanceof IUserPrincipleWithKeyStore) {
						IUserPrincipleWithKeyStore userPrincipleWithKeyStore = (IUserPrincipleWithKeyStore) userPrincipal;
						restEndpoint.setProperty(RestBasicAuthenticationHandler.KEYSTORE_TYPE,
								userPrincipleWithKeyStore.getKeyStoreType());
						restEndpoint.setProperty(RestBasicAuthenticationHandler.KEYSTORE,userPrincipleWithKeyStore.getKeyStoreBase64());
						restEndpoint.setProperty(RestBasicAuthenticationHandler.KEYSTORE_PASSWORD,
								userPrincipleWithKeyStore.getKeyStorePassword());
					}
				}
				break;

			case X_API_KEY:
				invoker.setAuthenticationHandler(new RestXApiKeyAuthenticationHandler());
				break;
			case OPEN_ID:
				IRestAuthenticationHandler restAuthenticationHandler = new OpenIdAuthenticationMessageHandler();
				String identityTokenStoreClassName = (String) UIActionFunctions.getOptionalDataItem(inputData, new ApplicationProcessDataType("", String.class));
				if (StringUtils.isEmpty(identityTokenStoreClassName)) {
					identityTokenStoreClassName = IDENTITY_TOKEN_STORE_CLASS_NAME;
				}
				try {
					IIdentityTokenStore tokenStore = (IIdentityTokenStore) ClassFactory.createInstance(identityTokenStoreClassName);
					((OpenIdAuthenticationMessageHandler) restAuthenticationHandler).setStore(tokenStore);
					invoker.setAuthenticationHandler(restAuthenticationHandler);
				} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
					logger.log(Logger.LOG_ERROR,
							"Failed to invoke REST service. Failed to create identityTokenStore for the class name "
									+ identityTokenStoreClassName,
							e);
					return CoreUIOutcomes.FAIL_OUTCOME;
				}
				break;

			default:
				// Not supported
				throw new UIProcessException(UIProcessException.CONFIGURATION_ERROR,
						"Unsupported authentication type: " + restEndpoint.getAuthenticationType());
			}
		}

		// Check and set the message handler
		String messageHandler = (String) UIActionFunctions.getOptionalDataItem(inputData, MESSAGE_HANDLER_DATA);
		if (restEndpoint.getProperty(MESSAGE_HANDLER_DATA.getName()) != null) {
			messageHandler = restEndpoint.getProperty(MESSAGE_HANDLER_DATA.getName());
		}

		if (messageHandler != null) {
			try {
				IRestMessageHandler handler = (IRestMessageHandler) ClassFactory.createInstance(messageHandler);
				invoker.setMessageHandler(handler);
			} catch (ClassCastException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
				logger.log(Logger.LOG_ERROR, "Failed to invoke REST service", e);

				return CoreUIOutcomes.FAIL_OUTCOME;
			}
		} else if (restEndpoint.getPayloadFormat() != null) {
			String responseObjectClassName = null;
			Class<?> responseObjectClass = null;
			switch (restEndpoint.getPayloadFormat()) {
			case ENTITY_JSON:
				if (!(requestObject instanceof IEntity) || restEndpoint.getPayloadFormat() != RestPayloadFormat.ENTITY_JSON) {
					invoker.setMessageHandler(new RestRawMessageHandler());
					break;
				}
				//Pass through

			case JSON_CLASS:
				// Try to infer what message handler to use from the request
				// if we can't, just use raw.
				if (requestObject instanceof IEntity || restEndpoint.getPayloadFormat() == RestPayloadFormat.ENTITY_JSON) {

					responseObjectClassName = (String) UIActionFunctions.getOptionalDataItem(inputData, RESPONSE_OBJECT_CLASS_NAME_DATA);

					if (responseObjectClassName == null) {
						invoker.setMessageHandler(new RestXmlToJsonMessageHandler());
					} else {
						try {
							responseObjectClass = Class.forName(responseObjectClassName);

							JsonSerializerUtil.Format jsonSerializerFormat = (JsonSerializerUtil.Format) UIActionFunctions
									.getOptionalDataItem(inputData, JSON_SERIALIZER_FORMAT_DATA);
							CustomRestJsonMessageHandler CustomRestJsonMessageHandler = null;
							if (jsonSerializerFormat != null) {
								CustomRestJsonMessageHandler = new CustomRestJsonMessageHandler(responseObjectClass,
										jsonSerializerFormat);
							} else {
								CustomRestJsonMessageHandler = new CustomRestJsonMessageHandler(responseObjectClass,
										Class.forName(UIActionFunctions.getOptionalDataItem(inputData, RESPONSE_ERROR_CLASS_NAME_DATA, ErrorDetails.class.getName())));
							}

							invoker.setMessageHandler(CustomRestJsonMessageHandler);
						} catch (ClassNotFoundException e) {
							throw new UIProcessException(e, UIProcessException.ERROR_RUNNING_ACTION, "Unable to load class");
						}
					}
				} else if (inputData.containsNotNullData(RESPONSE_OBJECT_CLASS_NAME_DATA)) {
					try {

						invoker.setMessageHandler(new CustomRestJsonMessageHandler(Class.forName((String) UIActionFunctions.getOptionalDataItem(inputData, RESPONSE_OBJECT_CLASS_NAME_DATA)),
									Class.forName(UIActionFunctions.getOptionalDataItem(inputData, RESPONSE_ERROR_CLASS_NAME_DATA, ErrorDetails.class.getName()))));

					} catch (ClassNotFoundException e) {
						logger.log(Logger.LOG_ERROR,
								"Failed to invoke REST service. Response class not found.", e);

						return CoreUIOutcomes.FAIL_OUTCOME;
					}
				} else {
					invoker.setMessageHandler(new RestRawMessageHandler());
				}

				break;
			case JSON:
				try {
					responseObjectClassName = (String) UIActionFunctions.getOptionalDataItem(inputData, RESPONSE_OBJECT_CLASS_NAME_DATA);

					if (StringUtils.notEmpty(responseObjectClassName)) {
						responseObjectClass = Class.forName(responseObjectClassName);
					}

					CustomRestJsonMessageHandler<?> CustomRestJsonMessageHandler = null;
					if (responseObjectClass != null) {
						JsonSerializerUtil.Format jsonSerializerFormat = (JsonSerializerUtil.Format) UIActionFunctions
								.getOptionalDataItem(inputData, JSON_SERIALIZER_FORMAT_DATA);

						if (jsonSerializerFormat != null) {
							CustomRestJsonMessageHandler = new CustomRestJsonMessageHandler(responseObjectClass,
									jsonSerializerFormat);
						} else {
							CustomRestJsonMessageHandler = new CustomRestJsonMessageHandler(responseObjectClass);
						}
					} else {
						CustomRestJsonMessageHandler = new CustomRestJsonMessageHandler(null, Class.forName(UIActionFunctions.getOptionalDataItem(inputData,
								RESPONSE_ERROR_CLASS_NAME_DATA, ErrorDetails.class.getName())));
					}

					invoker.setMessageHandler(CustomRestJsonMessageHandler);
				} catch (ClassNotFoundException e) {
					throw new UIProcessException(e, UIProcessException.ERROR_RUNNING_ACTION,
							"Unable to load class with name enactor.service.ResponseObjectClassName: "
									+ responseObjectClassName);
				}
				break;
			case JSON_CLASS_INFERRED:
				RestJsonInferredMessageHandler CustomRestJsonMessageHandler = null;
				CustomRestJsonMessageHandler = new RestJsonInferredMessageHandler();
				invoker.setMessageHandler(CustomRestJsonMessageHandler);

				break;
			case JSON_QNAME:
				RestQNameJsonInferredMessageHandler hanlder = new RestQNameJsonInferredMessageHandler();
				invoker.setMessageHandler(hanlder);

				break;

			case XML:
				invoker.setMessageHandler(new RestXmlMessageHandler());
			}
		} else {
			// Just use raw
			invoker.setMessageHandler(new RestRawMessageHandler());
		}

        if (connectTimeoutSecs != null) {
            invoker.setConnectTimeoutMilliseconds(connectTimeoutSecs * 1000);
        }
        
        if (socketTimeoutSecs != null) {
			invoker.setSocketTimeoutMilliseconds(socketTimeoutSecs * 1000);
		}

		setRestRequestMessageHandler(inputData, restEndpoint, invoker);

		setRestResponseMessageHandler(inputData, restEndpoint, invoker);

		Map<String, String> params = UIActionFunctions.getOptionalDataItem(inputData,
				PARAMETERS_DATA);

		Map<String, String> headers = UIActionFunctions.getOptionalDataItem(inputData, HEADERS_DATA);

		// Invoke the service
		try {
			Object responseObject = invoker.invoke(restEndpoint, requestObject, params, headers);
			if (invoker.getResponseHeaders() != null) {
				outputData.setData(RESPONSE_HEADERS_DATA, invoker.getResponseHeaders());
			}
			EndpointMonitor.logHealthyStatus(restEndpoint.getMonitoringTag());
			outputData.setData(CoreUIDataTypes.OBJECT_DATA, responseObject);

			return CoreUIOutcomes.SUCCESS_OUTCOME;
		} catch (RestServiceInvocationException e) {
			EndpointMonitor.logFailedStatus(restEndpoint.getMonitoringTag());
			final String errorMessage = "Failed to invoke REST service";
			Object errorResponse = e.getResponse();
			
			IErrorDetails errorDetails;
			if (errorResponse instanceof IErrorDetails) {
				errorDetails = ((IErrorDetails) errorResponse);
			} else {
				errorDetails = new ErrorDetails();
				errorDetails.setMessageText(errorMessage);
			}
			
			errorDetails.setException(e);
			
			outputData.setData(ErrorDetails.ENTITY_DATA_TYPE, errorResponse);
			outputData.setData(STATUS_CODE_DATA, e.getStatusCode());
			logger.log(Logger.LOG_ERROR, errorMessage, e);

			return CoreUIOutcomes.FAIL_OUTCOME;
		}
	}

	/**
	 * To keep the backward compatibility with the old single message handler, this will change the
	 * response message handler only if it is explicitly provided or the response payload format
	 * has been defined
	 *
	 * @param inputData
	 * @param restEndpoint
	 * @param invoker
	 * @throws UIProcessException
	 */
	private void setRestResponseMessageHandler(IApplicationProcessData inputData,
			IRestEndpoint restEndpoint, IRestServiceInvoker invoker) throws UIProcessException {
		String responseHanlderClassname = UIActionFunctions.getOptionalDataItem(inputData,
				RESPONSE_MESSAGE_HANDLER_DATA);
		if (restEndpoint.getProperty(RESPONSE_MESSAGE_HANDLER_DATA_NAME) != null) {
			responseHanlderClassname = restEndpoint.getProperty(RESPONSE_MESSAGE_HANDLER_DATA_NAME);
		}
		if (StringUtils.notEmpty(responseHanlderClassname)) {
			try {
				IRestResponseMessageHandler handler = (IRestResponseMessageHandler) ClassFactory
						.createInstance(responseHanlderClassname);
				invoker.setResponseMessageHandler(handler);
			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
				logger.log(Logger.LOG_ERROR,
						"The request class could not be found for :" + responseHanlderClassname, e);
				throw new UIProcessException(e,
						"The request class could not be found for the name :"
								+ responseHanlderClassname);
			}
		} else if (restEndpoint.getResponsePayloadFormat() != null) {
			invoker.setResponseMessageHandler(
					getDefaultResponseMessageHandler(restEndpoint, inputData));
		}
	}

	/**
	 * To keep the backward compatibility with the old single message handler, this will change the
	 * request message handler only if it is explicitly provided or the request payload format has
	 * been defined
	 *
	 * @param inputData
	 * @param restEndpoint
	 * @param invoker
	 * @throws UIProcessException
	 */
	private void setRestRequestMessageHandler(IApplicationProcessData inputData,
			IRestEndpoint restEndpoint, IRestServiceInvoker invoker) throws UIProcessException {
		String requestHanlderClassname = UIActionFunctions.getOptionalDataItem(inputData,
				REQUEST_MESSAGE_HANDLER_DATA);
		if (restEndpoint.getProperty(REQUEST_MESSAGE_HANDLER_DATA_NAME) != null) {
			requestHanlderClassname = restEndpoint.getProperty(REQUEST_MESSAGE_HANDLER_DATA_NAME);
		}
		if (StringUtils.notEmpty(requestHanlderClassname)) {
			try {
				IRestRequestMessageHandler handler = (IRestRequestMessageHandler) ClassFactory
						.createInstance(requestHanlderClassname);
				invoker.setRequestMessageHandler(handler);
			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
				logger.log(Logger.LOG_ERROR,
						"The request class could not be found for :" + requestHanlderClassname, e);
				throw new UIProcessException(e,
						"The request class could not be found for the name :"
								+ requestHanlderClassname);
			}
		} else if (restEndpoint.getRequestPayloadFormat() != null) {
			invoker.setRequestMessageHandler(getDefaultRequestMessageHandler(restEndpoint));
		}
	}

	/**
	 * Get request message handler for the defined request payload format
	 *
	 * @param restEndpoint
	 * @return
	 */
	private IRestRequestMessageHandler getDefaultRequestMessageHandler(IRestEndpoint restEndpoint) {
		switch (restEndpoint.getRequestPayloadFormat()) {
		case ENTITY_JSON:
		case JSON_CLASS:
			return new RestXmlToJsonMessageHandler();
		case JSON_CLASS_INFERRED:
			return new RestJsonInferredMessageHandler();
		case JSON:
			// Pass through
		case JSON_QNAME:
			return new RestQNameJsonInferredMessageHandler();
		case XML:
			return new RestXmlMessageHandler();
		case ZIP:
			return new RestFileRequestMessageHandler();
		case CONTENT_TYPE_INFERRED:
			throw new IllegalArgumentException("Request payload format cannot be inferred");
		default:
			return new RestRawMessageHandler();
		}
	}

	/**
	 * Get response message handler for the defined response payload format
	 *
	 * @param restEndpoint
	 * @param inputData
	 * @return
	 * @throws UIProcessException
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private IRestResponseMessageHandler getDefaultResponseMessageHandler(IRestEndpoint restEndpoint,
			IApplicationProcessData inputData) throws UIProcessException {
		switch (restEndpoint.getResponsePayloadFormat()) {
		case ENTITY_JSON:
		case JSON_CLASS:
			String responseObjectClassName = UIActionFunctions
					.getOptionalDataItem(inputData, RESPONSE_OBJECT_CLASS_NAME_DATA);
			if (responseObjectClassName == null) {
				return new RestXmlToJsonMessageHandler();
			} else {
				try {
					Class responseObjectClass = Class.forName(responseObjectClassName);

					JsonSerializerUtil.Format jsonSerializerFormat = (JsonSerializerUtil.Format) UIActionFunctions
							.getOptionalDataItem(inputData, JSON_SERIALIZER_FORMAT_DATA);
					if (jsonSerializerFormat != null) {
						return new CustomRestJsonMessageHandler(responseObjectClass,
								jsonSerializerFormat);
					} else {
						return new CustomRestJsonMessageHandler(responseObjectClass);
					}
				} catch (ClassNotFoundException e) {
					throw new UIProcessException(e, UIProcessException.ERROR_RUNNING_ACTION,
							"Unable to load class with name enactor.service.ResponseObjectClassName: "
									+ responseObjectClassName);
				}
			}
		case JSON_CLASS_INFERRED:
			return new RestJsonInferredMessageHandler();
		case JSON:
			// Pass through
		case JSON_QNAME:
			return new RestQNameJsonInferredMessageHandler();
		case XML:
			return new RestXmlMessageHandler();
		case CONTENT_TYPE_INFERRED:
			return new ContentTypeInferredResponseMessageHandler();
		case CSV:
			return new RestTextMessageHandler();
		case ZIP:
			throw new IllegalArgumentException("Response format type is not supported");
		default:
			return new RestRawMessageHandler();
		}
	}
}
