/*
 *  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;

import java.util.Iterator;

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

import org.apache.log4j.Logger;
import org.frameworkset.util.AntPathMatcher;
import org.frameworkset.util.Assert;
import org.frameworkset.util.PathMatcher;
import org.frameworkset.util.beans.BeansException;
import org.frameworkset.web.servlet.HandlerExecutionChain;
import org.frameworkset.web.servlet.HandlerMapping;
import org.frameworkset.web.util.UrlPathHelper;

/**
 * <p>Title: AbstractUrlHandlerMapping.java</p> 
 * <p>Description: </p>
 * <p>bboss workgroup</p>
 * <p>Copyright (c) 2008</p>
 * @Date 2010-9-24
 * @author biaoping.yin
 * @version 1.0
 */
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping{
	
	protected UrlPathHelper urlPathHelper = new UrlPathHelper();
	

	protected PathMatcher pathMatcher = new AntPathMatcher();
	protected static Logger logger = Logger.getLogger(AbstractUrlHandlerMapping.class);
	public void destroy()
	{
		super.destroy();
		if(handlerMap != null)
		{
			handlerMap.clear();handlerMap = null;
		}
		this.rootHandler = null;
		this.urlPathHelper = null;
		this.pathMatcher = null;
	}
	/**
	 * 
	 */
	private HandlerUrlMappingRegisterTable<String,HandlerMeta> handlerMap = new HandlerUrlMappingRegisterTable<String,HandlerMeta>();

	private HandlerMeta rootHandler;

	private boolean lazyInitHandlers;
	
	/**
	 * Set if URL lookup should always use the full path within the current servlet
	 * context. Else, the path within the current servlet mapping is used if applicable
	 * (that is, in the case of a ".../*" servlet mapping in web.xml).
	 * <p>Default is "false".
	 * @see org.frameworkset.web.util.UrlPathHelper#setAlwaysUseFullPath
	 */
	public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
		this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
	}
	
	public void setHandlerMap(HandlerUrlMappingRegisterTable handlerMap) {
		this.handlerMap = handlerMap;
		
	}

	/**
	 * Set if context path and request URI should be URL-decoded. Both are returned
	 * <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
	 * <p>Uses either the request encoding or the default encoding according
	 * to the Servlet spec (ISO-8859-1).
	 * @see org.frameworkset.web.util.UrlPathHelper#setUrlDecode
	 */
	public void setUrlDecode(boolean urlDecode) {
		this.urlPathHelper.setUrlDecode(urlDecode);
	}

	/**
	 * Set the UrlPathHelper to use for resolution of lookup paths.
	 * <p>Use this to override the default UrlPathHelper with a custom subclass,
	 * or to share common UrlPathHelper settings across multiple HandlerMappings
	 * and MethodNameResolvers.
	 * @see org.frameworkset.web.servlet.mvc.mutiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
	 */
	public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
		Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
		this.urlPathHelper = urlPathHelper;
	}

	/**
	 * Set the PathMatcher implementation to use for matching URL paths
	 * against registered URL patterns. Default is AntPathMatcher.
	 * @see org.frameworkset.web.util.AntPathMatcher
	 */
	public void setPathMatcher(PathMatcher pathMatcher) {
		Assert.notNull(pathMatcher, "PathMatcher must not be null");
		this.pathMatcher = pathMatcher;
	}

	/**
	 * Return the PathMatcher implementation to use for matching URL paths
	 * against registered URL patterns.
	 */
	public PathMatcher getPathMatcher() {
		return this.pathMatcher;
	}

	/**
	 * Set the root handler for this handler mapping, that is,
	 * the handler to be registered for the root path ("/").
	 * <p>Default is <code>null</code>, indicating no root handler.
	 */
	public void setRootHandler(HandlerMeta rootHandler) {
		this.rootHandler = rootHandler;
	}

	/**
	 * Return the root handler for this handler mapping (registered for "/"),
	 * or <code>null</code> if none.
	 */
	public HandlerMeta getRootHandler() {
		return this.rootHandler;
	}

	/**
	 * Set whether to lazily initialize handlers. Only applicable to
	 * singleton handlers, as prototypes are always lazily initialized.
	 * Default is "false", as eager initialization allows for more efficiency
	 * through referencing the controller objects directly.
	 * <p>If you want to allow your controllers to be lazily initialized,
	 * make them "lazy-init" and set this flag to true. Just making them
	 * "lazy-init" will not work, as they are initialized through the
	 * references from the handler mapping in this case.
	 */
	public void setLazyInitHandlers(boolean lazyInitHandlers) {
		this.lazyInitHandlers = lazyInitHandlers;
	}


	/**
	 * Look up a handler for the URL path of the given request.
	 * @param request current HTTP request
	 * @return the handler instance, or <code>null</code> if none found
	 */
	protected HandlerExecutionChain getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		HandlerExecutionChain handler = lookupHandler(lookupPath, request);
		if (handler == null) {
			// We need to care for the default handler directly, since we need to
			// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
			HandlerMeta rawHandler = null;
			if ("/".equals(lookupPath)) {
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath,lookupPath,request.getRequestURI());
			}
		}
		if (handler != null ) {
			logger.debug("Mapping [" + lookupPath + "] to handler '" + handler.getHandler().getHandlerName() + "'");
		}
		else if (handler == null) {
			logger.debug("No handler mapping found for [" + lookupPath + "]");
		}
		return handler;
	}

	/**
	 * Look up a handler instance for the given URL path.
	 * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
	 * and various Ant-style pattern matches, e.g. a registered "/t*" matches
	 * both "/test" and "/team". For details, see the AntPathMatcher class.
	 * <p>Looks for the most exact pattern, where most exact is defined as
	 * the longest path pattern.
	 * @param urlPath URL the bean is mapped to
	 * @param request current HTTP request (to expose the path within the mapping to)
	 * @return the associated handler instance, or <code>null</code> if not found
	 * @see #exposePathWithinMapping
	 * @see org.frameworkset.web.util.AntPathMatcher
	 */
	protected HandlerExecutionChain lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		// Direct match?
		HandlerMeta handler = this.handlerMap.get(urlPath);
		if (handler != null) {
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath,urlPath,request.getRequestURI());
		}
		// Pattern match?
		String bestPathMatch = null;
		for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
			String registeredPath = (String) it.next();
			if (getPathMatcher().match(registeredPath, urlPath) &&
					(bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
				bestPathMatch = registeredPath;
			}
		}
		if (bestPathMatch != null) {
			handler = this.handlerMap.get(bestPathMatch);
			validateHandler(handler, request);
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
			return buildPathExposingHandler(handler, pathWithinMapping,urlPath,request.getRequestURI());
		}
		// No handler found...
		return null;
	}

	/**
	 * Validate the given handler against the current request.
	 * <p>The default implementation is empty. Can be overridden in subclasses,
	 * for example to enforce specific preconditions expressed in URL mappings.
	 * @param handler the handler object to validate
	 * @param request current HTTP request
	 * @throws Exception if validation failed
	 */
	protected void validateHandler(HandlerMeta handler, HttpServletRequest request) throws Exception {
	}

	/**
	 * Build a handler object for the given raw handler, exposing the actual
	 * handler as well as the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}
	 * before executing the handler.
	 * <p>The default implementation builds a {@link HandlerExecutionChain}
	 * with a special interceptor that exposes the path attribute.
	 * @param rawHandler the raw handler to expose
	 * @param pathWithinMapping the path to expose before executing the handler
	 * @return the final handler object
	 */
	protected HandlerExecutionChain buildPathExposingHandler(HandlerMeta rawHandler, String pathWithinMapping,String mappingpath,String requesturi) {
		// Bean name or resolved handler?
		HandlerMeta ret = rawHandler;
		if (rawHandler.getHandler() instanceof String) {
			String handlerName = (String) rawHandler.getHandler();
			ret = new HandlerMeta();
			ret.setPathNames(rawHandler.getPathNames());
			ret.setHandler(getApplicationContext().getBeanObject(handlerName));
		}
		HandlerExecutionChain chain = new HandlerExecutionChain(ret);
		chain.addInterceptor(new PathExposingHandlerInterceptor(mappingpath,pathWithinMapping,requesturi));
		return chain;
	}

	/**
	 * Expose the path within the current mapping as request attribute.
	 * @param pathWithinMapping the path within the current mapping
	 * @param request the request to expose the path to
	 * @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
	 */
	protected void exposePathWithinMapping(String pathWithinMapping, HttpServletRequest request) {
		request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
	}
	
	/**
	 * Expose the path  the current mapping as request attribute.
	 * @param mappingPath the path within the current mapping
	 * @param request the request to expose the path to
	 * @see #HANDLER_MAPPING_PATH_ATTRIBUTE
	 */
	protected void exposeHandleMappingPath(String mappingPath, HttpServletRequest request) {
		request.setAttribute(HandlerMapping.HANDLER_MAPPING_PATH_ATTRIBUTE, mappingPath);
	}
	
	/**
	 * Expose the path  the current mapping as request attribute.
	 * @param mappingPath the path within the current mapping
	 * @param request the request to expose the path to
	 * @see #HANDLER_MAPPING_PATH_ATTRIBUTE
	 */
	public static void exposeAttribute(String key,Object value, HttpServletRequest request) {
		request.setAttribute(key, value);
	}
	
	/**
	 * Expose the path  the current mapping as request attribute.
	 * @param mappingPath the path within the current mapping
	 * @param request the request to expose the path to
	 * @see #HANDLER_MAPPING_PATH_ATTRIBUTE
	 */
	public static void removeAttribute(String key,HttpServletRequest request) {
		request.removeAttribute(key);
	}



	/**
	 * Register the specified handler for the given URL paths.
	 * @param urlPaths the URLs that the bean should be mapped to
	 * @param beanName the name of the handler bean
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected HandlerMeta registerHandler(String[] urlPaths, HandlerMeta meta) throws Exception, IllegalStateException {
		Assert.notNull(urlPaths, "URL path array must not be null");
		HandlerMeta ret = null;
		for (int j = 0; j < urlPaths.length; j++) {
			if(j == 0)
				ret = registerHandler(urlPaths[j], meta);
			else
				registerHandler(urlPaths[j], meta);
		}
		return ret;
	}

	/**
	 * Register the specified handler for the given URL path.
	 * @param urlPath the URL the bean should be mapped to
	 * @param handler the handler instance or handler bean name String
	 * (a bean name will automatically be resolved into the corresponding handler bean)
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected HandlerMeta registerHandler(String urlPath, HandlerMeta handler) throws Exception, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		HandlerMeta resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		if (!this.lazyInitHandlers && handler.getHandler() instanceof String) {
			String handlerName = (String) handler.getHandler();
			
			if (getApplicationContext().isSingleton(handlerName)) {
				resolvedHandler.setHandler(getApplicationContext().getBeanObject(handlerName));
			}
		}

		HandlerMeta mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler.getHandler() != resolvedHandler.getHandler()) {
				throw new IllegalStateException(
						"Cannot map handler [" + handler.getHandlerName() + "] to URL path [" + urlPath +
						"]: There is already handler [" + resolvedHandler.getHandlerName() + "] mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isDebugEnabled()) {
					logger.debug("Root mapping to handler [" + resolvedHandler.getHandlerName() + "]");
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isDebugEnabled()) {
					logger.debug("Default mapping to handler [" + resolvedHandler.getHandlerName() + "]");
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isDebugEnabled()) {
					logger.debug("Mapped URL path [" + urlPath + "] onto handler [" + resolvedHandler.getHandlerName() + "]");
				}
			}
		}
		return resolvedHandler;
	}


//	/**
//	 * Return the registered handlers as an unmodifiable Map, with the registered path
//	 * as key and the handler object (or handler bean name in case of a lazy-init handler)
//	 * as value.
//	 * @see #getDefaultHandler()
//	 */
//	public final Map getHandlerMap() {
//		return Collections.unmodifiableMap(this.handlerMap);
//	}


	/**
	 * Special interceptor for exposing the
	 * {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
	 * @link AbstractUrlHandlerMapping#exposePathWithinMapping
	 */
	private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {

		private final String pathWithinMapping;
		private final String mappingpath;
		private final String requesturi;

		public PathExposingHandlerInterceptor(String mappingpath,String pathWithinMapping,String requesturi) {
			this.pathWithinMapping = pathWithinMapping;
			this.mappingpath = mappingpath;
			this.requesturi = requesturi;
		}

		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMeta handler) {
			exposePathWithinMapping(this.pathWithinMapping, request);
			exposeHandleMappingPath(this.mappingpath, request);
			exposeAttribute(HANDLER_REQUESTURI_ATTRIBUTE,this.requesturi, request) ;
			return true;
		}
	}

}
