package gov.va.med.fw.ui.struts;

// Java/Servlet classes
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;

// Struts classes
import org.apache.struts.Globals;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.tiles.TilesRequestProcessor;

//Framework imports
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.validation.ActionValidation;
import gov.va.med.fw.validation.FormValidation;
import gov.va.med.fw.validation.ValidationFieldMessage;
import gov.va.med.fw.validation.ValidationMessage;
import gov.va.med.fw.validation.ValidationMessages;

/**
 * Added a hook to perform Form(custom) and Action(business) validation.
 * 
 * @author Muddaiah Ranga
 * @version 3.0
 */
public class CustomRequestProcessor extends TilesRequestProcessor {
	/**
	 * <p>
	 * If this request was not cancelled, and the request's
	 * {@link ActionMapping} has not disabled validation, call the
	 * <code>validate</code> method of the specified {@link ActionForm}, and
	 * forward to the input path if there were any errors. Return
	 * <code>true</code> if we should continue processing, or <code>false</code>
	 * if we have already forwarded control back to the input form.
	 * </p>
	 * 
	 * This method is modified to add hooks to call validateCustom() on the form
	 * bean and also to call processBusinessValidation() method on the action
	 * classes. These two method are called after performing the
	 * common-validator's validation.
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param response
	 *            The servlet response we are creating
	 * @param form
	 *            The ActionForm instance we are populating
	 * @param mapping
	 *            The ActionMapping we are using
	 * 
	 * @exception IOException
	 *                if an input/output error occurs
	 * @exception ServletException
	 *                if a servlet exception occurs
	 */
	protected boolean processValidate(HttpServletRequest request,
			HttpServletResponse response, ActionForm form, ActionMapping mapping)
			throws IOException, ServletException {
		if (form == null) {
			return (true);
		}
		// Was this request cancelled?
		if (request.getAttribute(Globals.CANCEL_KEY) != null) {
			if (log.isDebugEnabled()) {
				log.debug(" Cancelled transaction, skipping validation");
			}
			return (true);
		}

		// Has validation been turned off for this mapping?
		if (!mapping.getValidate()) {
			return (true);
		}

		// ***************************************************************************//
		// *************** Start of dispatch method validation check
		// *****************//
		// ***************************************************************************//

		// Check if the validation is required for the dispatch method
		if (mapping instanceof ServiceBrokerActionMapping
				&& !isMethodValidationEnabled(request, response, form,
						(ServiceBrokerActionMapping) mapping)) {
			return true;
		}

		// ***************************************************************************//
		// **************** End of dispatch method validation check
		// ******************//
		// ***************************************************************************//

		// Call the form bean's validation method
		if (log.isDebugEnabled()) {
			log.debug(" Validating input form properties");
		}
		ActionMessages errors = form.validate(mapping, request);
		if ((errors == null) || errors.isEmpty()) {
			if (log.isTraceEnabled()) {
				log.trace("  No declarative validation errors detected, accepting input");
			}

			// ***************************************************************************//
			// *************** Start of custom/business validations
			// **********************//
			// ***************************************************************************//

			errors = processFormAndActionValidation(request, response, form,
					mapping);
			if ((errors == null) || errors.isEmpty()) {
				if (log.isTraceEnabled()) {
					log.trace("  No custom/business validation errors detected, accepting input");
				}
				return (true);
			}

			// ***************************************************************************//
			// ***************** End of custom/business validations
			// **********************//
			// ***************************************************************************//
		}

		// Special handling for multipart request
		if (form.getMultipartRequestHandler() != null) {
			if (log.isTraceEnabled()) {
				log.trace("  Rolling back multipart request");
			}
			form.getMultipartRequestHandler().rollback();
		}

		// Was an input path (or forward) specified for this mapping?
		String input = mapping.getInput();
		if (input == null) {
			if (log.isTraceEnabled()) {
				log.trace("  Validation failed but no input form available");
			}
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
					getInternal().getMessage("noInput", mapping.getPath()));
			return (false);
		}

		// Save our error messages and return to the input form if possible
		if (log.isDebugEnabled()) {
			log.debug(" Validation failed, returning to '" + input + "'");
		}
		request.setAttribute(Globals.ERROR_KEY, errors);

		// Create or acquire the Action instance to perform the validation
		// callback
		Action action = processActionCreate(request, response, mapping);
		if (action instanceof ActionValidation) {
			((ActionValidation) action).failedValidationCallback(mapping, form,
					request, response);
		}

		if (moduleConfig.getControllerConfig().getInputForward()) {
			ForwardConfig forward = mapping.findForward(input);
			super.processForwardConfig(request, response, forward);
		} else {
			super.internalModuleRelativeForward(input, request, response);
		}

		return (false);
	}

	/**
	 * Checks if the validation is required for the dispatch method.
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param response
	 *            The servlet response we are creating
	 * @param form
	 *            The ActionForm instance we are populating
	 * @param mapping
	 *            The ActionMapping we are using
	 * @return
	 * 
	 * @throws IOException
	 * @throws ServletException
	 */
	private boolean isMethodValidationEnabled(HttpServletRequest request,
			HttpServletResponse response, ActionForm form,
			ServiceBrokerActionMapping mapping) throws IOException,
			ServletException {
		// Get the method list needs validation
		List methods = mapping.getValidateOnMethodList();
		if (methods == null || methods.size() == 0) {
			log.trace(" Validate on method list is null or empty");
			return true;
		}
		log.trace(" Method list that needs validation " + methods);

		// Get the method name parameter of the action-mapping, if null or empty
		// do nothing
		String parameter = mapping.getParameter();
		if (StringUtils.isEmpty(parameter)) {
			log.trace(" action-mapping parameter is null/empty");
			return true;
		}

		// Create or aquire the action
		Action action = processActionCreate(request, response, mapping);
		// If the action is the ServiceDelegatingActionProxy get the actual
		// action class name
		if (action instanceof ServiceDelegatingActionProxy) {
			action = ((ServiceDelegatingActionProxy) action)
					.getDelegateAction(mapping);
		}
		if (action instanceof ServiceBrokerAction) {
			try {
				String methodName = ((ServiceBrokerAction) action)
						.getMethodName(mapping, form, request, response,
								parameter);
				if (log.isTraceEnabled()) {
					log.trace(" Check validation is enabled for method "
							+ methodName);
				}
				// If the method name is not empty and method is not present in
				// the list, don't validate
				if (StringUtils.isNotEmpty(methodName)
						&& (!methods.contains(methodName))) {
					if (log.isTraceEnabled()) {
						log.trace(" Validation is not enabled for method "
								+ methodName);
					}
					return false;
				}
			} catch (Exception ex) {
				throw new ServletException(ex);
			}
		}
		return true;
	}

	/**
	 * Performs Form(custom) validation. If there are no form validation errors,
	 * then performs the Action(business) validation.
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param response
	 *            The servlet response we are creating
	 * @param form
	 *            The ActionForm instance we are populating
	 * @param mapping
	 *            The ActionMapping we are using
	 * 
	 * @return The custom or business action messages
	 * 
	 * @exception IOException
	 *                if an input/output error occurs
	 * @exception ServletException
	 *                if a servlet exception occurs
	 */
	protected ActionMessages processFormAndActionValidation(
			HttpServletRequest request, HttpServletResponse response,
			ActionForm form, ActionMapping mapping) throws IOException,
			ServletException {
		ActionMessages actionErrors = null;
		// Perform custom validation only when the form is implemented
		// FormValidation interface.
		if (form instanceof FormValidation) {
			actionErrors = ((FormValidation) form).validateForm(mapping,
					request);
		}
		if (actionErrors == null || actionErrors.isEmpty()) {
			if (log.isTraceEnabled()) {
				log.trace("  No Form validation errors detected, accepting input");
			}

			// Create or acquire the Action instance to process this request
			Action action = processActionCreate(request, response, mapping);
			try {
				if (action instanceof ActionValidation) {
					actionErrors = ((ActionValidation) action).validateAction(
							mapping, form, request, response);
				}
				if (((actionErrors == null) || actionErrors.isEmpty())
						&& log.isTraceEnabled()) {
					log.trace("  No Action validation errors detected, accepting input");
				}
			} catch (Exception ex) {
				log.error(
						" Errors occured while performing business validation.",
						ex);
				throw new ServletException(ex);
			}
		}
		return actionErrors;
	}

	/**
	 * Convert ValidationMessages in to ActionMessages.
	 * 
	 * @param validationMessages
	 *            the validation service messages
	 * @return the ActionMessages
	 * 
	 * @throws Exception
	 *             when ValidationMessages contains an invalid object.
	 */
	protected ActionMessages convertValidationMessages(
			ValidationMessages validationMessages) throws Exception {
		ActionMessages actionMessages = null;
		if (validationMessages != null && !validationMessages.isEmpty()) {
			log.debug("Action validation messages exists.");

			// Convert ValidationService specific errors in to Struts
			// ActionMessages.
			actionMessages = new ActionMessages();
			Iterator iter = validationMessages.get();
			ValidationMessage validationError = null;
			ValidationFieldMessage validationFieldError = null;
			ActionMessage actionMessage = null;
			while (iter.hasNext()) {
				Object message = iter.next();
				if (message instanceof ValidationFieldMessage) {
					validationFieldError = (ValidationFieldMessage) message;
					actionMessage = new ActionMessage(
							validationFieldError.getKey(),
							validationFieldError.getValues());
					actionMessages.add(validationFieldError.getFieldName(),
							actionMessage);
				} else if (message instanceof ValidationMessage) {
					validationError = (ValidationMessage) message;
					actionMessage = new ActionMessage(validationError.getKey(),
							validationError.getValues());
					actionMessages.add("global", actionMessage);
				} else {
					throw new Exception("Invalid object in ValidationErrors.");
				}
			}
		}
		return actionMessages;
	}

}
