/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.tools.guiframework.view;

import com.iplanet.jato.ApplicationServletBase;
import com.iplanet.jato.RequestContext;
import com.iplanet.jato.RequestContextImpl;
import com.iplanet.jato.RequestManager;
import com.iplanet.jato.view.View;
import com.iplanet.jato.view.ViewBean;
import com.iplanet.jato.NavigationException;
import com.iplanet.jato.util.RootCauseException;

import com.sun.enterprise.tools.guiframework.event.descriptors.EventDescriptor;
import com.sun.enterprise.tools.guiframework.exception.FrameworkError;
import com.sun.enterprise.tools.guiframework.exception.FrameworkException;
import com.sun.enterprise.tools.guiframework.exception.ViewDescriptorHolder;
import com.sun.enterprise.tools.guiframework.util.LogUtil;
import com.sun.enterprise.tools.guiframework.view.descriptors.ViewDescriptor;
import com.sun.enterprise.tools.guiframework.view.event.ErrorEvent;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.EventObject;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.xml.sax.EntityResolver;


public abstract class BaseServlet extends ApplicationServletBase {

    /**
     *	This method provides a last resort spot to handle any uncaught
     *	exceptions or errors.  The preferred way to handle exceptions or errors
     *	is to register and "error" event to dispatch the handling to the
     *	designated handler, however, if that fails or you do not do this, this
     *	method will be invoked.  The ErrorEvent that is passed in will contain
     *	information about what went wrong.  The View and ViewDescriptor
     *	contained in the Error event may be null as there was no handler
     *	defined to take care of this exception.
     *
     *	@param	errorEvent	The ErrorEvent object describing the exception
     */
    protected abstract void handleUncaughtException(ErrorEvent errorEvent);


    /**
     *	This method should return a String URL pointing to the location of the
     *	XML file containing the ViewDescriptor definitions.
     */
    protected abstract URL getViewXMLURL();


    /**
     *	This method should return a String to prefix before all JSP paths.
     *	This method may soon be deprecated, I recommend returning "" from this
     *	method to avoid problems later.
     */
    protected abstract String getJSPRoot();


    /**
     *	@return The package name of the Servlet.
     */
    protected abstract String getPackageName();


    /**
     *	This method provides access to the DTD_URL_BASE location.  By default,
     *	this method will look for a Servlet init parameter named DTD_URL_BASE
     *	("dtdURLBase"), however, sub-classes may override this and provide a
     *	different means to resolve this URL path.
     */
    protected String getDTDURLBase(ServletConfig config) {
	// Get the dtd base URL
	String base = config.getInitParameter(DTD_URL_BASE);
	if ((base == null) && LogUtil.isLoggable(LogUtil.FINER)) {
	    LogUtil.log(LogUtil.FINER,
		"framework.servletInitNotFound", DTD_URL_BASE);
	}
	return base;
    }


    /**
     *	This method provides returns the entity resolver that can be used to look
     *	up view xml files. By default no entityresolver is specfied. sub-classes
     *	may chose to provide their own entityresolver. An example is 
     *   com.sun.enterprise.tools.guiframework.view.ViewXMLEntityResolver
     */
    
    protected EntityResolver getViewXMLEntityResolver() {
        return null;
    }
    
    /**
     *	Set up the ViewDescriptorManager
     */
    public void init(ServletConfig config) throws ServletException {
	super.init(config);

	// Setup the ViewDescriptorManager
	ViewDescriptorManager mgr = ViewDescriptorManager.getInstance();
	mgr.setDTDURLBase(getDTDURLBase(config));
	mgr.setViewDescriptorURL(getViewXMLURL());
        mgr.setViewXMLEntityResolver(getViewXMLEntityResolver());
// FIXME: Consider removing jsp root concept
	mgr.setJSPRoot(getJSPRoot());
    }


    /**
     *	This method creates a new DescriptorViewManager.  If you want to use
     *	your own sub-class, you can override this method.  The default
     *	implementation of DescriptorViewManager uses ViewDescriptorManager for
     *	creating new instances of DescriptorViews.
     *
     *	@param rc	The RequestContext
     *
     *	@return	The DescriptorViewManager used to manage DescriptorViews
     */
    protected DescriptorViewManager newViewManagerInstance(RequestContext rc) {
	return new DescriptorViewManager(rc, getPackageName());
    }


    /**
     *	This method sets the DescriptorViewManager as the ViewBeanManager.
     */
    protected void initializeRequestContext(RequestContext rc) {
	super.initializeRequestContext(rc);

        // Set a view bean manager in the request context.  This must be
	// done at the module level because the view bean manager is
	// module specifc.
        DescriptorViewManager vm = newViewManagerInstance(rc);
	((RequestContextImpl)rc).setViewBeanManager(vm);
    }


    /**
     *	This method attempts to figure out the "pageName"
     */
    protected String getPageName(RequestContext rc) {
	HttpServletRequest request = rc.getRequest();
	String pageName = request.getParameter(PARAM_HANDLER_BEAN);
	if (pageName == null) {
	    pageName = getPageName(request, request.getPathInfo());
	}
	return pageName;
    }

    /**
     *
     */
    protected String getPageName(HttpServletRequest request, String pathInfo) {
	String pageName = null;
	if (pathInfo != null) {
	    pageName = parsePathInfo(pathInfo);
	}

	// Nothing specified, use default
	if (pageName == null) {
	    pageName = getDefaultHandlerName(request);
	}
	return pageName;
    }


    /**
     *	This method is overriden to catch errors and handle them.
     */
    protected void processRequest(String pageName, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	try {
	    super.processRequest(pageName, request, response);
	} catch (Throwable ex) {
	    try {
		handleException(RequestManager.getRequestContext(), ex);
	    } catch (Throwable ex2) {
		handleException(RequestManager.getRequestContext(), ex2);
	    }
	}
    }


    /**
     *
     */
    protected void onUncaughtException(RequestContext rc, Exception ex) throws ServletException, IOException {
	try {
	    handleException(rc, ex);
	} catch (Throwable ex2) {
	    handleException(rc, ex2);
	}
    }


    /**
     *	This method exists to deal with the known exceptions that don't do
     *	things right (they have their own way of chaining exceptions and don't
     *	support the standard mechanism of exception chaining.
     *
     *	@param	ex	The exception to attempt to get the cause for
     */
    protected Throwable getRootCause(Throwable ex) {
	if (ex instanceof RootCauseException) {
	    // JATO doesn't use getCause(),
	    //	they use their own: getRootCause()
	    Throwable jatoRoot = ((RootCauseException)ex).getRootCause();
	    try {
		ex.initCause(jatoRoot);
	    } catch (Exception ignore) {
		// We're just doing our best to do what JATO didn't
	    }
	    ex = jatoRoot;
	} if (ex instanceof ServletException) {
	    // Servlet Exception doesn't do things right either
	    Throwable servletRoot = ((ServletException)ex).getRootCause();
	    try {
		ex.initCause(servletRoot);
	    } catch (Exception ignore) {
		// We're just doing our best to do what ServletException didn't
	    }
	    ex = servletRoot;
	} else {
	    ex = ex.getCause();
	}
	return ex;
    }


    /**
     *
     */
    protected void handleException(RequestContext rc, Throwable ex) {
	// Hopefully we have a FrameworkException that has info we need
	// If a forward was called, JATO may have wrapped our exception... may
	// be possible to wrap in other ways as well.
	View view = null;
	ViewDescriptor desc = null;
	Throwable tmpEx = ex;
	while (tmpEx != null) {
	    if (tmpEx instanceof ViewDescriptorHolder) {
		ViewDescriptorHolder vdh = (ViewDescriptorHolder)tmpEx;
		desc = vdh.getResponsibleViewDescriptor();
		view = vdh.getResponsibleView();
		break;
	    }
	    tmpEx = getRootCause(tmpEx);
	}

	// The view might be null (even if we have a FrameworkException)
	if (view == null) {
	    String pageName = getPageName(rc);
	    if (pageName != null) {
		try {
		    view = rc.getViewBeanManager().getViewBean(pageName);
		} catch (Exception ex2) {
		    // Ignore, b/c we are already trying to show an exception
		}
	    }
	}

	// The desc might be null (even if we have a FrameworkException)
	if ((desc == null) && (view != null)) {
	    if (view instanceof DescriptorContainerView) {
		desc = ((DescriptorContainerView)view).getViewDescriptor();
	    }
	}

	// Handle the exception
	handleException(rc, ex, desc, view);
    }


    /**
     *	<P>This method gathers important information having to do with the
     *	exception that was thrown.  This information is then stored in an
     *	ErrorEvent to be passed to a handler designed to handle ErrorEvents.
     *	This information is also sent to the log.</P>
     *
     *	<P>After this method gathers the exception information, it then
     *	dispatches to an error handler, if found.  If no error handler is found
     *	it will invoke the method 'handleUncaughtException' on the Servlet.</P>
     *
     *	@param	ex	The Exception / Error
     *	@param	desc	The ViewDescriptor responsible for the Exception/Error
     *	@param	view	The View responsible for the Exception / Error
     */
    public void handleException(RequestContext rc, Throwable ex, ViewDescriptor desc, View view) {
    	// First find the ViewBean (may not be view & view may be null)
	View vb = view;
	HttpServletRequest request = rc.getRequest();
	if (vb == null) {
	    if (desc != null) {
		ViewDescriptor topDesc = desc;
		while (topDesc.getParent() != null) {
		    topDesc = topDesc.getParent();
		}
		try {
		    vb = topDesc.getView(rc);
		} catch (Exception ignoreEx) {
		    // Ignore, b/c we're just trying another way to get the VB
		}
	    }
	}

	// Try to get the top-most View (above code is not enough)
	while ((vb != null) && !(vb instanceof ViewBean)) {
	    vb = vb.getParent();
	}
	if (vb == null) {
	    // This checks for a special case where include() has been
	    // called... this means there is NO ViewBean for a parent
	    // we attempt to find the ViewBean by...
	    String pageName = getPageName(request, request.getPathInfo());
//		(String)request.getAttribute(PATH_INFO_ATTRIBUTE));
	    try {
		vb = rc.getViewBeanManager().getViewBean(pageName);
	    } catch (Exception notFoundException) {
		// ignore
	    }
	}
	if (vb == null) {
	    // ViewBean not in view tree!!!!
	    String msg = "Top View was not a ViewBean!";
	    if (desc != null) {
		// We have a ViewDescriptor, give top ViewDescriptor name
		ViewDescriptor topDesc = desc;
		while (topDesc.getParent() != null) {
		    topDesc = topDesc.getParent();
		}
		msg = "Top View (as described by ViewDescriptor: '"+
		    topDesc.getName()+"') was not a ViewBean!";
	    }
	    ex = new FrameworkError(msg, ex);
	}

	// Get the root cause
	Throwable root = ex;
	Throwable next = ex;
	while (next != null) {
	    root = next; // root will be non-null at the end
	    next = getRootCause(next);
	}

	// Get the root cause message / class name
	// Don't show a root message if it is the same as the top
	String rootMessage = (root == ex) ? null : root.getMessage();
	String rootClass = (root == null) ? null : root.getClass().getName();

	// Create/save the full stack trace
	StackTraceElement elts[] = root.getStackTrace();
	int len = elts.length;
	StringBuffer fullTrace = new StringBuffer();
	for (int count=0; count<len; count++) {
	    fullTrace.append("\t"+elts[count].toString()+"\n");
	}

	// Get/set the regular stack trace
	StringWriter writer = new StringWriter();
	ex.printStackTrace(new PrintWriter(writer));
	String regTrace = writer.toString();

	// Look for Error Handler, first find ViewDescriptor
	if (desc == null) {
	    View tmpView = view;
	    String childName = null;
	    while ((tmpView != null) && !(tmpView instanceof DescriptorContainerView)) {
		childName = tmpView.getName();
		tmpView = tmpView.getParent();
	    }
	    if (tmpView != null) {
		desc = ((DescriptorContainerView)tmpView).getViewDescriptor();
		if (childName != null) {
		    // Try to walk back down the hierachy (one level only, b/c
		    // this is the first DescriptorContainerView -- any other
		    // containers below will not work)
		    if (desc.getChildDescriptor(childName) != null) {
			desc = desc.getChildDescriptor(childName);
		    }
		}
	    }
	}
	if (desc != null) {
	    // Ok, at this point we have a ViewDescriptor... find the nearest
	    // error handler
	    ViewDescriptor handlerDesc = desc;
	    ViewDescriptor tmpDesc = desc;
	    while ((handlerDesc != null) && (handlerDesc.getEventDescriptor(EventDescriptor.TYPES.ERROR) == null)) {
	    	// No error handler for this view descriptor, look at parent
		tmpDesc = handlerDesc.getParent();
		if ((tmpDesc == null) && (vb != null) && (vb instanceof DescriptorContainerView)) {
		    // If we don't find one, check the ViewBean (we might not
		    // have access via the ViewDescriptors... such is the case
		    // when dealing with pagelets).
		    tmpDesc = ((DescriptorContainerView)vb).getViewDescriptor();
		}
		handlerDesc = tmpDesc;
	    }
	    if (handlerDesc != null) {
		View descView = null;
		// We found the nearest Error Event Handler! Use it.
		try {
		    descView = handlerDesc.getView(rc);
		} catch (Throwable ignoreThis) {
		    ex = new FrameworkError(
			"Unable to get View for ViewDescriptor '"+
			handlerDesc.getName()+"'", ex);
		}
		ErrorEvent errorEvent = new ErrorEvent(
		    descView, handlerDesc, (ViewBean)vb, ex, view, desc,
		    ex.getMessage(), ex.getClass().getName(), rootMessage,
		    rootClass, fullTrace.toString(), regTrace);
		DescriptorViewHelper.dispatchEvent(
		    rc, descView, handlerDesc,
		    handlerDesc.getEventDescriptor(
			EventDescriptor.TYPES.ERROR),
		    errorEvent);
		return;
	    }
	}

	// No Error handlers defined. :(
	// We'll try to use a default method on the Servlet
	ErrorEvent errorEvent = new ErrorEvent(
	    null, null, (ViewBean)vb, ex, view, desc,
	    ex.getMessage(), ex.getClass().getName(), rootMessage,
	    rootClass, fullTrace.toString(), regTrace);
	handleUncaughtException(errorEvent);
    }

    /**
     *	<p> This method is the "last chance" to invoke anything before
     *	    processing stops.  This implementation delegates to "afterRequest"
     *	    handlers that are registered to the ViewBean (top-level
     *	    ViewDescriptor).</p>
     *
     *	This doesn't work, I can't get the VB that was forwardTo()'d.  I can
     *	override this method in DescriptorViewBeanBase (and other base
     *	classes), but that defeats the point of putting this code here.  I can
     *	also make ViewBeans RequestCompletionListeners and invoke this code.
     *	Since there currently isn't a requirement for page-level "afterRequest"
     *	events, I'm not going to implement this.  This functionality does work
     *	at the command (button / href) level, this seems more desirable anyway.
    protected void onAfterRequest(RequestContext rc) {
	View vb = (View) rc.getRequest().getAttribute("viewBean");
	if (vb instanceof DescriptorContainerView) {
	    ViewDescriptor desc =
		((DescriptorContainerView) vb).getViewDescriptor();
	    if (desc != null) {
		// Fire After Request Event
// FIXME: Consider providing an EventObject, FYI: JATO does not
		DescriptorViewHelper.dispatchEvent(rc, vb, desc,
		    desc.getEventDescriptor(EventDescriptor.TYPES.AFTER_REQUEST),
		    (EventObject) null);
	    }
	}
    }
     */

    /**
     *	"dtdURLBase" is a servlet init parameter that must be defined so that
     *	relative paths to dtds (the view.dtd) can be resolved.  This does not
     *	have any effect on dtds used by other systems such as Lockhart.
     */
    public static final String DTD_URL_BASE = "dtdURLBase";

    /**
     *	When an include() is called the PATH_INFO is set via a Request
     *	attribute named "javax.servlet.include.path_info".  This constant is
     *	set to this name.
     */
    public static final String PATH_INFO_ATTRIBUTE =
	"javax.servlet.include.path_info";
}
