/*
 * 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.RequestCompletionListener;
import com.iplanet.jato.RequestContext;
import com.iplanet.jato.ViewBeanManager;
import com.iplanet.jato.view.ContainerView;
import com.iplanet.jato.view.View;
import com.iplanet.jato.view.ViewBean;

import java.util.HashMap;
import java.util.Map;

import com.sun.enterprise.tools.guiframework.exception.FrameworkException;
import com.sun.enterprise.tools.guiframework.util.LogUtil;
import com.sun.enterprise.tools.guiframework.view.descriptors.ViewDescriptor;


public class DescriptorViewManager extends ViewBeanManager {

    /*
     * Create an instance with the specified request context and module 
     * package name
     *
     * @param context	The request context for the current request
     * @param basePackage
     *			The fully qualified package name of the module servlet
     *			creating this object.  This package name is used to 
     *			later qualify local <code>ViewBean</code> lookups.
     */
    public DescriptorViewManager(RequestContext context, String basePackage) {
	super(context, basePackage);
	_reqCtx = context;				// private in super
    }


    /**
     *	<P>JATO's short-sidedness did not consider that 1 ViewBean can be
     *	associated with more than 1 JSP (with different fields).  So they think
     *	that classname is unique... it's not, of course.  So we will use "name"
     *	to store a unique identifier for ViewBean type.  And so this over-riden
     *	method for getViewBean(String) will NOT expect a class name but a
     *	ViewBean name (since class name will normally be the same in our case).
     *
     *	<P>This method is internally invoked when using JATO's useViewBean tag.
     *  You should specify the desired ViewBean "name" when using it, not the
     *	class name.
     *
     *	@param name	The name of the ViewBean to get
     *
     *	@return A ViewBean of the requested type
     */
    public ViewBean getViewBean(String name) throws ClassNotFoundException {
	// Delegate to getView(View parent, String name)
	ViewBean vb = (ViewBean)getView(null, name);

	// Check to make sure we found the ViewBean
	if (vb == null) {
	    // Fall back to default JATO way, using class name...
	    try {
		_useClassName = true;
		if(name != null && name.endsWith("ViewBean")) {
			//Strig off the ViewBean part, and the package name part for JATO to handle it correctly.
			int beginIndex = name.lastIndexOf('.') + 1; 
			int endIndex = name.length() - VB_LEN;
			name = name.substring(beginIndex, endIndex); 
		}
		vb = super.getLocalViewBean(name);
	    } catch (ClassNotFoundException ex) {
		throw new FrameworkException("Unable to locate ViewBean: "+name, ex);
	    } finally {
		_useClassName = false;
	    }

	    // Register it so we always get the same one
	    registerInstance(vb);
	}

	// Return the ViewBean we created
	return vb;
    }


    /**
     *	<P>This method is used indirectly in the case where a ViewDescriptor is
     *	not found and the default JATO way of locating a VB kicks in.  In this
     *	case, super.getLocalViewBean() is called from the getViewBean(String)
     *	method.  Which winds up calling this method after creating the class
     *	name.  In general, this method should NOT be invoked directly as it is
     *	the intention of this implemenation of JATO NOT to refer to ViewBeans
     *	by class name.  Instead a ViewDescriptor should be declared for every
     *	ViewBean you want to use.  The ViewDescriptor's getInstance() method
     *	is capable of creating the desired ViewBean.</P>
     *
     *	<P>This method is also used directly by the JSP's useViewBean tag.  In
     *	this case, we want to do the regular XML lookup.  We are using the
     *	_useClassName flag to do the right thing.</P>
     */
    public ViewBean getViewBeanByClassName(String className) throws ClassNotFoundException {
	if (_useClassName) {
	    // Invoke the correct getViewBean(String) method... in this case we
	    // want the super's getViewBean() b/c we are in the process of
	    // falling back to old-school JATO's way of doing things.
	    return super.getViewBean(className);
	}

	// This request is probably coming from the JSP, do a regular lookup
	return getViewBean(className);
    }


    /**
     *	This method allows you to get a View (including ViewBean) by supplying
     *	the name.  If the requested View is NOT a ViewBean, then you should
     *	supply the parent for the View.  The Views are cached per request so
     *	that you are guarenteed to get the same instance if you ask for the
     *	same "name" twice during a request.
     *
     *	@param parent	This is only used for non-ViewBeans, should be the
     *			container of the requested View.
     *	@param name	The name of the View you would like to create.
     *
     *	@return	The requested View, or null if no descriptor for the View was
     *		found.
     */
    public View getView(View parent, String name) {
	if (name == null) {
	    throw new IllegalArgumentException("Name cannot be null!");
	}

	// If we've created it during the request before, use the same one
	View view = (View)_instances.get(name);
	if (view != null) {
	    return view;
	}

	// Get the ViewDescriptorManager
	ViewDescriptorManager mgr = ViewDescriptorManager.getInstance();

	// mgr.clearCache() here forces reloading on every request
	//mgr.clearCache();

        // Get the ViewDescriptor from the manager
        ViewDescriptor desc = mgr.getViewDescriptor(name);

	// Make sure we got it
        if (desc == null) {
	    if (LogUtil.isLoggable(LogUtil.FINE)) {
		LogUtil.log(LogUtil.FINE, "framework.getViewDescriptor", name);
	    }

            // This method ONLY handles creating from a ViewDescriptor
            return null;
        }

	// Tracing message...
	if (LogUtil.isLoggable(LogUtil.FINER)) {
	    LogUtil.log(LogUtil.FINER, "trace.gotViewDescriptor", name);
	}

	return getView(parent, name, desc);
    }


    /**
     *	This method allows you to get a View (including ViewBean) by supplying
     *	the name.  If the requested View is NOT a ViewBean, then you should
     *	supply the parent for the View.  The Views are cached per request so
     *	that you are guarenteed to get the same instance if you ask for the
     *	same "name" twice during a request.
     *
     *	@param parent	This is only used for non-ViewBeans, should be the
     *			container of the requested View.
     *	@param name	The name of the View you would like to create.
     *	@param desc	The view descriptor
     *
     *	@return	The requested View
     */
    public View getView(View parent, String name, ViewDescriptor desc) {
	// If we've created it during the request before, use the same one
	View view = (View)_instances.get(name);
	if (view != null) {
	    return view;
	}

	// Make sure we have a ViewDescriptor
	if (desc == null) {
	    throw new IllegalArgumentException("ViewDescriptor was null!!");
	}

	// Fire our "beforeCreate" event.  This provides a spot for
	// initializing stuff before we create something.  This, of course, is
	// different than beforeDisplay which happens after the child is
	// created.  This is useful for doing Model initialization-type
	// operations.
	DescriptorViewHelper.beforeCreate(_reqCtx,
	    ((parent instanceof DescriptorContainerView) ?
		(DescriptorContainerView)parent : null), desc);

	// Create a new instance
	view = desc.getInstance(_reqCtx, (ContainerView)parent, name);

	// Register it so we always get the same one
	registerInstance(view);

	// Tracing message...
	if (LogUtil.isLoggable(LogUtil.FINER)) {
	    LogUtil.log(LogUtil.FINER, "trace.createdView",
		new Object[] {name, view});
	}

	// Fire our "afterCreate" event.  This provides a spot for
	// initializing stuff after we create something.  This is useful for
	// doing anything you would like to do once for this View per request
	// and need a reference to the View.
	DescriptorViewHelper.afterCreate(_reqCtx, view, desc);

	// Return the View we created
	return view;
    }


    /**
     *	Simply pass the "name" of the ViewBean you want.  This works the same
     *	as getViewBean(String).
     *
     *	@param name	The name of the ViewBean to get
     *
     *	@return A ViewBean of the requested type
     *
     *	@see #getViewBean(String)
     */
    public ViewBean getLocalViewBean(String name) throws ClassNotFoundException {
	return getViewBean(name);
    }


    /**
     *	This method is not recommended by this implementation (this
     *	implementation DOES NOT store ViewBeans or Views by Class).
     *
     *	@deprecated Please read the DescriptorViewManager documentation, this
     *	    version does not recommend this method.  If you really did mean
     *	    to get a ViewBean by Class, either declare a descriptor and use
     *	    that, or call getViewBean(String) which will fallback to here (not
     *	    recommended).
     */
    public ViewBean getViewBean(Class cls) {
	if (_useClassName) {
	    _useClassName = false;
	    return super.getViewBean(cls);
	}
	throw new FrameworkException("This method is unsupported!");
    }


    /**
     *	Register the specified view bean with the <code>ViewBeanManager</code>.
     *	Subsequent requests for the same view bean within the current request 
     * 	will return the registered instance.  This method overrides the super
     *	class in order to store the instances by NAME (instead of class).  All
     *	ViewBean "names" should be unique (these are defined in the
     *	descriptors).  Also, View's are also registered in the same namespace.
     *	
     *
     *	@param bean	The <code>ViewBean</code> to register
     */
    public void registerInstance(ViewBean bean) {
	registerInstance((View)bean);
    }


    /**
     *	This method provides the actual implementation, the ViewBean version
     *	delegates to this method.  This allows View's to be stored as well.
     *
     *	@param	view	The View to register.
     */
    public void registerInstance(View view) {
	if (!_instances.containsKey(view.getName())) {
	    _instances.put(view.getName(), view);
	    if (view instanceof RequestCompletionListener) {
		_reqCtx.addRequestCompletionListener((RequestCompletionListener)view);
	    }
	}
    }


    protected RequestContext _reqCtx;
    protected boolean _useClassName = false;

    public static final int VB_LEN = "ViewBean".length();
    private Map _instances = new HashMap();
}
