/* Portlets.java

	Purpose:
		
	Description:
		
	History:
		Fri Apr  1 13:57:54     2005, Created by tomyeh

Copyright (C) 2005 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.web.portlet;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.lang.SystemException;
import org.zkoss.web.servlet.Servlets;
import org.zkoss.web.util.resource.ExtendletContext;

/**
 * Utilities to handle portlet.
 *
 * @author tomyeh
 */
public class Portlets {
	private static final Logger log = LoggerFactory.getLogger(Portlets.class);

	/**
	 * Includes the resource at the specified URI.
	 * It enhances RequestDispatcher to allow the inclusion with
	 * a parameter map -- actually converting parameters to a query string
	 * and appending it to uri.
	 *
	 * <p>NOTE: don't include query parameters in uri.
	 *
	 * @param uri the URI to include. It is OK to relevant (without leading
	 * '/'). If starts with "/", the context path of request is assumed.
	 * To reference to foreign context, use "~ctx/" where ctx is the
	 * context path of the foreign context (without leading '/').
	 * @param params the parameter map; null to ignore
	 * @param mode one of {@link #OVERWRITE_URI}, {@link #IGNORE_PARAM},
	 * and {@link #APPEND_PARAM}. It defines how to handle if both uri
	 * and params contains the same parameter.
	 */
	public static final void include(PortletContext ctx, RenderRequest request, RenderResponse response, String uri,
			Map params, int mode) throws IOException, PortletException {
		final PortletRequestDispatcher disp = getRequestDispatcher(ctx, uri, params, mode);
		if (disp == null)
			throw new PortletException("No dispatcher available to include " + uri);
		disp.include(request, response);
	}

	/** Returns the request dispatch of the specified URI.
	 *
	 * @param ctx the context used to resolve a foreign context.
	 * It is required only if uri starts with "~".
	 * @param uri the URI to include. It is OK to relevant (without leading
	 * '/'). If starts with "/", the context path of request is assumed.
	 * To reference to foreign context, use "~ctx/" where ctx is the
	 * context path of the foreign context (without leading '/').
	 * @param params the parameter map; null to ignore
	 * @param mode one of {@link #OVERWRITE_URI}, {@link #IGNORE_PARAM},
	 * and {@link #APPEND_PARAM}. It defines how to handle if both uri
	 * and params contains the same parameter.
	 */
	public static final PortletRequestDispatcher getRequestDispatcher(PortletContext ctx, String uri, Map params,
			int mode) throws PortletException {
		return new ParsedURI(ctx, uri).getRequestDispatcher(params, mode);
	}

	/** Returns the resource of the specified uri.
	 * Unlike PortletContext.getResource, it handles "~" like
	 * {@link #getRequestDispatcher} did.
	 */
	public static final URL getResource(PortletContext ctx, String uri) throws MalformedURLException {
		return new ParsedURI(ctx, uri).getResource();
	}

	/** Returns the resource stream of the specified uri.
	 * Unlike PortletContext.getResource, it handles "~" like
	 * {@link #getRequestDispatcher} did.
	 */
	public static final InputStream getResourceAsStream(PortletContext ctx, String uri) {
		return new ParsedURI(ctx, uri).getResourceAsStream();
	}

	/** Used to resolve "~" in URI. */
	private static class ParsedURI {
		/** The portlet context, null if _svlctx or _extctx is need. */
		private PortletContext _prtctx;
		/** External context if ~xxx/ is specified. */
		private ServletContext _svlctx;
		/** The extended context. */
		private ExtendletContext _extctx;
		private String _uri;

		private ParsedURI(final PortletContext ctx, final String uri) {
			if (uri != null && uri.length() > 0 && uri.charAt(0) == '~') { //refer to foreign context
				final int j = uri.indexOf('/', 1);
				final String ctxroot;
				if (j >= 0) {
					ctxroot = "/" + uri.substring(1, j);
					_uri = uri.substring(j);
				} else {
					ctxroot = "/" + uri.substring(1);
					_uri = "/";
				}

				final ServletContext svlctx = getServletContext(ctx);
				_extctx = Servlets.getExtendletContext(svlctx, ctxroot.substring(1));
				if (_extctx == null) {
					_svlctx = svlctx;
					_svlctx = _svlctx.getContext(ctxroot);
					if (_svlctx == null)
						throw new SystemException("Context not found or not visible to " + ctx + ": " + ctxroot);
				}
			} else {
				_prtctx = ctx;
				_uri = uri;
			}
		}

		private PortletRequestDispatcher getRequestDispatcher(Map params, int mode) {
			if (_extctx == null && _svlctx == null && _prtctx == null) //not found
				return null;

			final String uri = generateURI(_uri, params, mode);
			if (_prtctx != null)
				return _prtctx.getRequestDispatcher(uri);

			final RequestDispatcher rd = _svlctx != null ? _svlctx.getRequestDispatcher(uri)
					: _extctx.getRequestDispatcher(uri);
			return ServletPortletDispatcher.getInstance(rd);
		}

		private URL getResource() throws MalformedURLException {
			return _prtctx != null ? _prtctx.getResource(_uri)
					: _svlctx != null ? _svlctx.getResource(_uri) : _extctx != null ? _extctx.getResource(_uri) : null;
		}

		private InputStream getResourceAsStream() {
			return _prtctx != null ? _prtctx.getResourceAsStream(_uri)
					: _svlctx != null ? _svlctx.getResourceAsStream(_uri)
							: _extctx != null ? _extctx.getResourceAsStream(_uri) : null;
		}
	}

	private static final ServletContext getServletContext(PortletContext ctx) {
		return PortletServletContext.getInstance(ctx);
	}

	/** Whether to overwrite uri if both uri and params contain the same
	 * parameter.
	 * Used by {@link #generateURI}
	 */
	public static final int OVERWRITE_URI = Servlets.OVERWRITE_URI;
	/** Whether to ignore params if both uri and params contain the same
	 * parameter.
	 * Used by {@link #generateURI}
	 */
	public static final int IGNORE_PARAM = Servlets.IGNORE_PARAM;
	/** Whether to append params if both uri and params contain the same
	 * parameter. In other words, they both appear as the final query string.
	 * Used by {@link #generateURI}
	 */
	public static final int APPEND_PARAM = Servlets.APPEND_PARAM;

	/** Generates URI by appending the parameters.
	 * @param params the parameters to append to the query string
	 * @param mode one of {@link #OVERWRITE_URI}, {@link #IGNORE_PARAM},
	 * and {@link #APPEND_PARAM}. It defines how to handle if both uri
	 * and params contains the same parameter.
	 * mode is used only if both uri contains query string and params is
	 * not empty.
	 */
	public static final String generateURI(String uri, Map params, int mode) {
		return Servlets.generateURI(uri, params, mode);
	}
}
