/*
 *  Copyright 2008 biaoping.yin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.frameworkset.web.servlet.handler.annotations;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;

import org.apache.log4j.Logger;
import org.frameworkset.http.converter.HttpMessageConverter;
import org.frameworkset.util.ReflectionUtils;
import org.frameworkset.util.annotations.MethodData;
import org.frameworkset.web.servlet.HandlerMapping;
import org.frameworkset.web.servlet.ModelMap;
import org.frameworkset.web.servlet.handler.AbstractUrlHandlerMapping;
import org.frameworkset.web.servlet.handler.HandlerMeta;
import org.frameworkset.web.servlet.handler.HandlerUtils;
import org.frameworkset.web.servlet.support.RequestContext;

import com.frameworkset.util.BeanUtils;

/**
 * <p>
 * Title: HandlerMethodInvoker.java
 * </p>
 * <p>
 * Description:
 * </p>
 * <p>
 * bboss workgroup
 * </p>
 * <p>
 * Copyright (c) 2008
 * </p>
 * 
 * @Date 2010-10-24
 * @author biaoping.yin
 * @version 1.0
 */
public class HandlerMethodInvoker {

	/**
	 * We'll create a lot of these objects, so we don't want a new logger every
	 * time.
	 */
	protected final static Logger logger = Logger.getLogger(HandlerMethodInvoker.class);

	private final HandlerMethodResolver methodResolver;
//
////	private final WebBindingInitializer bindingInitializer;
//
//	private final SessionAttributeStore sessionAttributeStore;
//
//	private final ParameterNameDiscoverer parameterNameDiscoverer;
//
//	private final WebArgumentResolver[] customArgumentResolvers;
	protected HttpMessageConverter[] messageConverters;

//	private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();

//	public HandlerMethodInvoker(HandlerMethodResolver methodResolver,HttpMessageConverter[] messageConverters) {
//		this(methodResolver, null,messageConverters);
//	}

//	public HandlerMethodInvoker(HandlerMethodResolver methodResolver,
//			WebBindingInitializer bindingInitializer,
//			HttpMessageConverter[] messageConverters) {
//		this(messageConverters,methodResolver, 
////				bindingInitializer,
//				new DefaultSessionAttributeStore(), null);
//	}

	public HandlerMethodInvoker(HttpMessageConverter[] messageConverters,
//			,
			HandlerMethodResolver methodResolver
//			,
//			WebBindingInitializer bindingInitializer,
//			SessionAttributeStore sessionAttributeStore,
//			ParameterNameDiscoverer parameterNameDiscoverer,
//			WebArgumentResolver... customArgumentResolvers
			) {

		this.methodResolver = methodResolver;
//		this.bindingInitializer = bindingInitializer;
//		this.sessionAttributeStore = sessionAttributeStore;
//		this.parameterNameDiscoverer = parameterNameDiscoverer;
//		this.customArgumentResolvers = customArgumentResolvers;
		this.messageConverters = messageConverters;
	}

	public final Object invokeHandlerMethod(MethodData handlerMethod,
			HandlerMeta handler, HttpServletRequest request,
			HttpServletResponse response, PageContext pageContext,
			ModelMap implicitModel) throws Exception {

		// Method handlerMethodToInvoke =
		// BridgeMethodResolver.findBridgedMethod(handlerMethod);
		try {
			boolean debug = logger.isDebugEnabled();
			// for (Method attributeMethod :
			// this.methodResolver.getModelAttributeMethods()) {
			// Method attributeMethodToInvoke =
			// BridgeMethodResolver.findBridgedMethod(attributeMethod);
			// Object[] args = resolveHandlerArguments(attributeMethodToInvoke,
			// handler, webRequest,pageContext, implicitModel);
			// if (debug) {
			// logger.debug("Invoking model attribute method: " +
			// attributeMethodToInvoke);
			// }
			// Object attrValue = doInvokeMethod(attributeMethodToInvoke,
			// handler, args);
			// String attrName =
			// AnnotationUtils.findAnnotation(attributeMethodToInvoke,
			// org.frameworkset.util.annotations.ModelAttribute.class).name();
			// if ("".equals(attrName)) {
			// Class resolvedType =
			// GenericTypeResolver.resolveReturnType(attributeMethodToInvoke,
			// handler.getClass());
			// attrName =
			// Conventions.getVariableNameForReturnType(attributeMethodToInvoke,
			// resolvedType, attrValue);
			// }
			// implicitModel.addAttribute(attrName, attrValue);
			// }
			// System.out.println("_________________"+request.getMethod());
			
			/**
			 * HttpServletRequest request, HttpServletResponse
			 * response,PageContext pageContext, MethodData handlerMethod,
			 * ModelMap model,Validator[] validators
			 */
			if(handlerMethod.getMethodInfo().isPagerMethod())//
			{
				AbstractUrlHandlerMapping.exposeAttribute(HandlerMapping.PAGER_METHOD_FLAG_ATTRIBUTE, new Boolean(true), request);
			}
			if(!handlerMethod.getMethodInfo().isDefinePageSize())
			{
				String cookieid = RequestContext.getPagerSizeCookieID(request, null);
				int defaultSize = RequestContext.getPagerSize(request,  HandlerMapping.DEFAULT_PAGE_SIZE,cookieid);
				AbstractUrlHandlerMapping.exposeAttribute(HandlerMapping.PAGER_PAGESIZE_FLAG_ATTRIBUTE, new Integer(defaultSize), request);
				AbstractUrlHandlerMapping.exposeAttribute(org.frameworkset.web.servlet.HandlerMapping.PAGER_COOKIEID_ATTRIBUTE, 
						cookieid, request);
				AbstractUrlHandlerMapping.exposeAttribute(org.frameworkset.web.servlet.HandlerMapping.PAGER_CUSTOM_PAGESIZE_ATTRIBUTE, 
						HandlerMapping.DEFAULT_PAGE_SIZE, request);
				
			}
			
			Object[] args = HandlerUtils.buildMethodCallArgs(request, response,
					pageContext, handlerMethod, implicitModel, null,messageConverters);
			// Object[] args = resolveHandlerArguments(handlerMethodToInvoke,
			// handler, webRequest, pageContext,implicitModel);
			if (debug) {
				logger.debug("Invoking request handler method: "
						+ handlerMethod.getMethodInfo().getMethod());
			}
			if(!implicitModel.hasErrors())//没有数据绑定错误
			{
				return doInvokeMethod(handlerMethod.getMethodInfo().getMethod(),
						handler, args);
			}
			else//有绑定数据错误
			{
				return doInvokeMethod(handlerMethod.getMethodInfo().getMethod(),
						handler, args);
			}
		} catch (IllegalStateException ex) {
			// Throw exception with full handler method context...
			throw new HandlerMethodInvocationException(handlerMethod
					.getMethodInfo().getMethod(), ex);
		}
		catch (InvocationTargetException ex) {
			// The handler method threw an exception.
			return handleException(handler,request, response,  pageContext, ex.getTargetException(),implicitModel);
		}
		catch (Exception ex) {
			// The binding process threw an exception.
			return handleException(handler,request, response,  pageContext, ex,implicitModel);
		}
		finally
		{
			if(handlerMethod.getMethodInfo().isPagerMethod())
			{
//				AbstractUrlHandlerMapping.removeAttribute(HandlerMapping.PAGER_METHOD_FLAG_ATTRIBUTE, request);
//				AbstractUrlHandlerMapping.removeAttribute(HandlerMapping.PAGER_PAGESIZE_FLAG_ATTRIBUTE, request);
//				AbstractUrlHandlerMapping.removeAttribute(HandlerMapping.PAGER_COOKIEID_ATTRIBUTE, request);
			}
		}
		
	}
	
	/**
	 * We've encountered an exception thrown from a handler method.
	 * Invoke an appropriate exception handler method, if any.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param ex the exception that got thrown
	 * @return a ModelAndView to render the response
	 */
	private Object handleException(HandlerMeta delegate,HttpServletRequest request, HttpServletResponse response, PageContext pageContext, Throwable ex,ModelMap implicitModel)
			throws Exception {

		Method handler = methodResolver.getExceptionHandler(ex);
		if (handler != null) {
			 {
				 logger.debug("Invoking exception handler [" + handler + "] for exception: " + ex);
			}
			try {
				Object returnValue = handler.invoke(delegate.getHandler(), request, response,pageContext, ex,implicitModel);
				return returnValue;
//				return massageReturnValueIfNecessary(returnValue);
			}
			catch (InvocationTargetException ex2) {
				logger.error("Original exception overridden by exception handling failure", ex);
				ReflectionUtils.rethrowException(ex2.getTargetException());
			}
			catch (Exception ex2) {
				logger.error("Failed to invoke exception handler method", ex2);
			}
		}
		else {
			// If we get here, there was no custom handler or we couldn't invoke it.
			ReflectionUtils.rethrowException(ex);
		}
		throw new IllegalStateException("Should never get here");
	}

	

	/**
	 * Determine whether the given value qualifies as a "binding candidate",
	 * i.e. might potentially be subject to bean-style data binding later on.
	 */
	protected boolean isBindingCandidate(Object value) {
		return (value != null && !value.getClass().isArray()
				&& !(value instanceof Collection) && !(value instanceof Map) && !BeanUtils
				.isSimpleValueType(value.getClass()));
	}

	private Object doInvokeMethod(Method method, HandlerMeta target, Object[] args)
			throws Exception {
		ReflectionUtils.makeAccessible(method);
		try {
			return method.invoke(target.getHandler(), args);
		} catch (InvocationTargetException ex) {
			if(logger.isDebugEnabled())
			{
				try {
					StringBuilder b = new StringBuilder();
					b.append("args:");
					if(args != null && args.length > 0)
					{
						int i = 0;
						for(Object arg:args)
						{
							b.append("arg[").append(i).append("]=").append(arg).append("\n");
							i ++;
						}
					}
					
					logger.debug(b.toString());
				} catch (Exception e) {
					 
				}
			}
			ReflectionUtils.rethrowException(ex.getTargetException());
		}
		catch (IllegalArgumentException ex) {
			if(logger.isDebugEnabled())
			{
				try {
					StringBuilder b = new StringBuilder();
					b.append("args:");
					if(args != null && args.length > 0)
					{
						int i = 0;
						for(Object arg:args)
						{
							b.append("arg[").append(i).append("]=").append(arg).append("\n");
							i ++;
						}
					}
					
					logger.debug(b.toString());
				} catch (Exception e) {
					 
				}
			}
			ReflectionUtils.rethrowException(ex);
		}
		
		throw new IllegalStateException("Should never get here");
	}

	protected void raiseMissingParameterException(String paramName,
			Class paramType) throws Exception {
		throw new IllegalStateException("Missing parameter '" + paramName
				+ "' of type [" + paramType.getName() + "]");
	}

	protected void raiseSessionRequiredException(String message)
			throws Exception {
		throw new IllegalStateException(message);
	}

//	protected WebDataBinder createBinder(NativeWebRequest webRequest,
//			Object target, String objectName) throws Exception {
//
//		return new ServletRequestDataBinder(target, objectName);
//	}

	protected HttpMessageConverter[] getMessageConverters() {
		return messageConverters;
	}

	// /**
	// * Create a new binder instance for the given command and request.
	// * <p>Called by <code>bind</code>. Can be overridden to plug in custom
	// * ServletRequestDataBinder subclasses.
	// * <p>The default implementation creates a standard
	// ServletRequestDataBinder,
	// * and invokes <code>initBinder</code>. Note that <code>initBinder</code>
	// * will not be invoked if you override this method!
	// * @param request current HTTP request
	// * @param command the command to bind onto
	// * @return the new binder instance
	// * @throws Exception in case of invalid state or arguments
	// * @see #bind
	// * @see #initBinder
	// */
	// protected ServletRequestDataBinder createBinder(HttpServletRequest
	// request, Object command) throws Exception {
	// ServletRequestDataBinder binder = new ServletRequestDataBinder(command,
	// getCommandName(command));
	// initBinder(request, binder);
	// return binder;
	// }

	// /**
	// * Return the command name to use for the given command object.
	// * <p>Default is "command".
	// * @param command the command object
	// * @return the command name to use
	// * @see #DEFAULT_COMMAND_NAME
	// */
	// protected String getCommandName(Object command) {
	// return DEFAULT_COMMAND_NAME;
	// }
	//
	// /** Default command name used for binding command objects: "command" */
	// public static final String DEFAULT_COMMAND_NAME = "command";
	

	
}
