/*
 * Copyright 2004-2008 the original author or authors.
 *
 * 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.springframework.webflow.mvc.servlet;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler;
import org.springframework.webflow.context.servlet.FlowUrlHandler;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;

/**
 * Implementation of {@link org.springframework.web.servlet.HandlerMapping} that follows a simple convention for
 * creating URL path mappings from the <i>ids</i> of registered {@link FlowDefinition flow definitions}.
 * 
 * This implementation returns a FlowHandler that invokes a flow if the current request path matches the id of a flow in
 * the configured {@link FlowDefinitionRegistry}. Alternatively, a custom {@link FlowHandler} may also be registered
 * with in containing ApplicationContext with that id and it will be returned. This allows for more control over the
 * invocation of a flow from Spring MVC environment.
 * 
 * Null is returned in the case of no flow id match, allowing the next handler mapping in the chain to execute.
 * 
 * @author Keith Donald
 */
public class FlowHandlerMapping extends AbstractHandlerMapping {

	private static final Log logger = LogFactory.getLog(FlowHandlerMapping.class);

	private FlowDefinitionRegistry flowRegistry;

	private FlowUrlHandler flowUrlHandler;

	/**
	 * Returns the registry of flows to query when this mapping is tested.
	 * @return the flow definition registry
	 */
	public FlowDefinitionRegistry getFlowRegistry() {
		return flowRegistry;
	}

	/**
	 * Sets the registry of flows to query when this mapping is tested. Optional. If not set, this handler mapping will
	 * look in the containing application context for a bean with id <code>flowRegistry</code>.
	 * @param flowRegistry the flow definition registry
	 */
	public void setFlowRegistry(FlowDefinitionRegistry flowRegistry) {
		this.flowRegistry = flowRegistry;
	}

	/**
	 * Returns the configured flow url handler.
	 */
	public FlowUrlHandler getFlowUrlHandler() {
		return flowUrlHandler;
	}

	/**
	 * Sets the flow URL handler, which allows customization for how the flow id is determined for each request tested
	 * by this mapping. Defaults to a {@link DefaultFlowUrlHandler}.
	 * @param flowUrlHandler the flow URL handler
	 */
	public void setFlowUrlHandler(FlowUrlHandler flowUrlHandler) {
		this.flowUrlHandler = flowUrlHandler;
	}

	protected void initServletContext(ServletContext servletContext) {
		Assert.notNull(flowRegistry, "The FlowRegistry to query when mapping requests is required");
		if (flowUrlHandler == null) {
			flowUrlHandler = new DefaultFlowUrlHandler();
		}
	}

	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		String flowId = flowUrlHandler.getFlowId(request);
		if (flowId == null) {
			return null;
		}
		if (getApplicationContext().containsBean(flowId)) {
			Object handler = getApplicationContext().getBean(flowId);
			if (handler instanceof FlowHandler) {
				if (logger.isDebugEnabled()) {
					logger.debug("Mapping request with URI '" + request.getRequestURI() + "' to flow with id '"
							+ flowId + "'; custom FlowHandler " + handler + " will manage flow execution");
				}
				return handler;
			}
		}
		if (flowRegistry.containsFlowDefinition(flowId)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Mapping request with URI '" + request.getRequestURI() + "' to flow with id '" + flowId
						+ "'");
			}
			return createDefaultFlowHandler(flowId);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("No flow mapping found for request with URI '" + request.getRequestURI() + "'");
		}
		return null;
	}

	/**
	 * Factory method that returns the default flow handler for the flow with the given id. Subclasses may override to
	 * return their own custom default FlowHandler.
	 * @param flowId the id of the flow to handle invocation of
	 * @return the default flow handler
	 */
	protected FlowHandler createDefaultFlowHandler(String flowId) {
		return new DefaultFlowHandler(flowId);
	}

	private static class DefaultFlowHandler extends AbstractFlowHandler {

		private String flowId;

		public DefaultFlowHandler(String flowId) {
			this.flowId = flowId;
		}

		public String getFlowId() {
			return flowId;
		}
	}

}