/*
 * $Id: UserMenuAction.java 54929 2004-10-16 16:38:42Z germuska $ 
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 * 
 * 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.apache.struts.webapp.tiles.portal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.tiles.ComponentContext;
import org.apache.struts.tiles.ComponentDefinition;
import org.apache.struts.tiles.Controller;
import org.apache.struts.tiles.DefinitionsUtil;
import org.apache.struts.tiles.actions.TilesAction;
import org.apache.struts.tiles.beans.MenuItem;

/**
 * This controller load user menu settings and put them in tile context.
 * If menu settings are not defined for user, defined them based on tiles
 * attributes used as default.
 *
 * This implementation extends Struts Action, and also define Tiles Controller interface.
 * This allows to use it as well as a controller type or a controller url. If used
 * as controller type, Struts Action functionality are not availables.
 *
 * Tiles input attributes :
 * <ul>
 *   <li>title : menu title</li>
 *   <li>items : Menu entries used as default when user settings is created</li>
 *   <li>defaultChoice : Menus or menu entries porposed as choice to user</li>
 *   <li>storeUnderName : Store user settings under provided name in session context [optional]</li>
 *   <li></li>
 * </ul>
 * Tiles output attributes :
 * <ul>
 *   <li>title : menu title</li>
 *   <li>items : Menu items to display</li>
 *   <li></li>
 * </ul>
 *
 * @version $Rev: 54929 $ $Date: 2004-10-16 17:38:42 +0100 (Sat, 16 Oct 2004) $
 */
public final class UserMenuAction extends TilesAction implements Controller {

	/** 
	 * Commons Logging instance.
	 */
	private static Log log = LogFactory.getLog(UserMenuAction.class);

	/** 
	 * Tile attribute containing name used to store user settings in session 
	 * context.
	 */
	public static String USER_SETTINGS_NAME_ATTRIBUTE = "userSettingsName";

	/** 
	 * Default name used to store settings in session context. 
	 */
	public static String DEFAULT_USER_SETTINGS_NAME =
		"tiles.examples.portal.USER_MENU_SETTINGS";

	/** 
	 * Default name used to store menu catalog in application scope.
	 */
	public static String DEFAULT_MENU_CATALOG_NAME =
		"tiles.examples.portal.MenuCatalog";

	/** 
	 * Tile attribute containing name used to store menu catalog in application 
	 * scope.
	 */
	public static String MENU_CATALOG_NAME_ATTRIBUTE = "catalogName";

	/** 
	 * Tile attribute containing name of the settings definition used to 
	 * initialize catalog. 
	 */
	public static final String CATALOG_SETTING_ATTRIBUTE = "catalogSettings";

	/** 
	 * Tile attribute containing items to render.
	 */
	public static String USER_ITEMS_ATTRIBUTE = "items";

	/**
	 * Struts' action perform().
	 * Process the specified HTTP request, and create the corresponding HTTP
	 * response (or forward to another web component that will create it).
	 * Return an <code>ActionForward</code> instance describing where and how
	 * control should be forwarded, or <code>null</code> if the response has
	 * already been completed.
	 *
	 * @param context The current Tile context, containing Tile attributes.
	 * @param mapping The ActionMapping used to select this instance.
	 * @param form The optional ActionForm bean for this request (if any).
	 * @param request The HTTP request we are processing.
	 * @param response The HTTP response we are creating.
	 *
	 * @exception Exception if the application business logic throws
	 *  an exception
	 * @since Struts 1.1
	 */
	public ActionForward execute(
		ComponentContext context,
		ActionMapping mapping,
		ActionForm form,
		HttpServletRequest request,
		HttpServletResponse response)
		throws Exception {

		this.execute(
			context,
			request,
			response,
			getServlet().getServletContext());

		return null;
	}

	/**
	 * Method associated to a tile and called immediately before tile is included.
	 * @param tileContext Current tile context.
	 * @param request Current request
	 * @param response Current response
	 * @param servletContext Current servlet context
	 */
	public void perform(
		ComponentContext context,
		HttpServletRequest request,
		HttpServletResponse response,
		ServletContext servletContext)
		throws ServletException, IOException {

		log.debug("Enter action UserMenuAction");

		// Load user settings from user context
		MenuSettings settings = getUserSettings(request, context);

		// Set parameters for rendering page
		context.putAttribute(USER_ITEMS_ATTRIBUTE, settings.getItems());

		log.debug("settings=" + settings);
		log.debug("Exit action UserMenuAction");

	}

	/**
	 * Load user setting.
	 * This implementation load setting from user context.
	 * If settings are not found, initialized them from default items defined
	 * in Tile's context.
	 * If settings are not found, initialized them.
	 */
	public static MenuSettings getUserSettings(
		HttpServletRequest request,
		ComponentContext context)
		throws ServletException {

		// Get current session.
		HttpSession session = request.getSession();

		// Retrieve attribute name used to store settings.
		String userSettingsName =
			(String) context.getAttribute(USER_SETTINGS_NAME_ATTRIBUTE);

		if (userSettingsName == null) {
			userSettingsName = DEFAULT_USER_SETTINGS_NAME;
		}

		// Get user list from user context
		MenuSettings settings =
			(MenuSettings) session.getAttribute(userSettingsName);

		// If settings don't exist, create and initialize them
		// Initialization is done from context attribute denoted by ITEMS
		if (settings == null) {
			// List doesn't exist, create it and initialize it from Tiles parameters
			settings = new MenuSettings();
			try {
				settings.addItems(
					(List) context.getAttribute(USER_ITEMS_ATTRIBUTE));
			} catch (ClassCastException ex) {
				throw new ServletException("Can't initialize user menu : default items must be a list of items");
			}

			// Save user settings in session
			session.setAttribute(userSettingsName, settings);
		}

		return settings;
	}

	/**
	 * Get catalog of available menu entries.
	 * This implementation creates catalog list from the provided menu bar 
	 * entries.
	 */
	public static List getCatalog(
		ComponentContext context,
		HttpServletRequest request,
		ServletContext servletContext)
		throws ServletException {

		// Retrieve name used to store catalog in application context.
		// If not found, use default name
		String catalogName =
			(String) context.getAttribute(MENU_CATALOG_NAME_ATTRIBUTE);

		if (catalogName == null) {
			catalogName = DEFAULT_MENU_CATALOG_NAME;
		}

		// Get catalog from context
		List catalog = (List) servletContext.getAttribute(catalogName);

		// If not found, initialize it from provided default menu
		if (catalog == null) {
			Object menuBar = context.getAttribute(CATALOG_SETTING_ATTRIBUTE);
			if (menuBar == null) {
				throw new ServletException(
					"Attribute '"
						+ CATALOG_SETTING_ATTRIBUTE
						+ "' must be set. It define entries used in catalog");
			}

			catalog = new ArrayList();
			extractItems(catalog, menuBar, request, servletContext);
			if (catalog.size() == 0) {
				throw new ServletException("Can't initialize menu items catalog");
			}

			// save it for future use
			servletContext.setAttribute(catalogName, catalog);
		}

		return catalog;
	}

	/**
	 * Extract menu items from passed object. Items are stored in 
	 * <code>result</code> parameter. 
	 * This method allows to create a list of menu entries from existing menus.
	 * Check object type class :
	 * <li>
	 *   <ul>MenuItems : add it</ul>
	 *   <ul>ComponentDefinition : get attribute items, or list if not found.
	 *       Call ExtractItems with resulting attribute.
	 *   </ul>
	 *   <ul>List : iterate on list, and call ExtractItems for each element.
	 * </li>
	 * @param result result list (should be initialized)
	 * @param object object to add (MenuItems, Definition, ...)
	 * @param request current request
	 * @param servletContext current servlet context.
	 */
	private static void extractItems(
		List result,
		Object object,
		HttpServletRequest request,
		ServletContext servletContext) {

		log.debug("Extract menu item from '" + object + "'");

		if (object instanceof String) { // definition name
			try {
				ComponentDefinition def =
					DefinitionsUtil.getDefinition(
						(String) object,
						request,
						servletContext);

				extractItems(result, def, request, servletContext);

			} catch (Exception ex) { // silently fail
			}

		} else if (object instanceof List) {
			List list = (List) object;
			Iterator iter = list.iterator();
			while (iter.hasNext()) {
				extractItems(result, iter.next(), request, servletContext);
			}

		} else if (object instanceof ComponentDefinition) {
			ComponentDefinition definition = (ComponentDefinition) object;
			Object attribute = definition.getAttribute("items");
			if (attribute == null) {
				attribute = definition.getAttribute("list");
			}

			if (attribute == null) {
				return;
			}

			extractItems(result, attribute, request, servletContext);

		} else if (object instanceof MenuItem) {
			result.add(object);
		}
	}

	/**
	 * @see org.apache.struts.tiles.Controller#execute(org.apache.struts.tiles.ComponentContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext)
	 */
	public void execute(
		ComponentContext tileContext,
		HttpServletRequest request,
		HttpServletResponse response,
		ServletContext servletContext)
		throws Exception {
            
		log.debug("Enter action UserMenuAction");

		// Load user settings from user context
		MenuSettings settings = getUserSettings(request, tileContext);

		// Set parameters for rendering page
		tileContext.putAttribute(USER_ITEMS_ATTRIBUTE, settings.getItems());

		log.debug("settings=" + settings);
		log.debug("Exit action UserMenuAction");

	}

}
