/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.ui.rpc;

import java.io.*;
import java.util.*;
import org.apache.log4j.*;
import org.apache.regexp.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.RootConfig;
import com.silverwrist.venice.ui.helpers.SessionControl;
import com.silverwrist.venice.ui.helpers.ThrowableContent;
import com.silverwrist.venice.ui.script.*;
import com.silverwrist.venice.util.ServiceProvider;
import com.silverwrist.venice.util.XMLLoader;

public class XmlRpcMethod
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Category logger = Category.getInstance(XmlRpcMethod.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private REProgram method_pgm;       // regular expression program to match method name
  private String remap_value = null;  // remap value
  private Class obj_class = null;     // "class" handler for object
  private String script_name = null;  // script name to use as a handler
  private Map raw_env;                // "raw" environment
  private int session_param = -1;     // session parameter

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public XmlRpcMethod(RootConfig root, Element cfg) throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    String method_name = loader.configGetAttribute(cfg,"name");
    try
    { // compile the method name into a regular expression program
      RECompiler comp = new RECompiler();
      method_pgm = comp.compile("^" + method_name + "$");

    } // end try
    catch (RESyntaxException rese)
    { // not a valid regular expression program
      logger.error("not a valid RE expression: " + method_name,rese);
      throw new ConfigException("method name \"" + method_name + "\" not valid regular expression",cfg);

    } // end catch

    DOMElementHelper cfg_h = new DOMElementHelper(cfg);
    Element sub;

    if ((sub = cfg_h.getSubElement("object"))!=null)
    { // object handler element is present
      DOMElementHelper sub_h = new DOMElementHelper(sub);
      if (sub_h.hasAttribute("class"))
      { // the class is valid - try and load it
	try
	{ // load the class referenced by the handler
	  obj_class = Class.forName(sub.getAttribute("class"));
	  if (!(XmlRpcDispatch.class.isAssignableFrom(obj_class)))
	    throw new ConfigException("object handler class \"" + sub.getAttribute("class")
				      + "\" is not an XmlRpcDispatch class",sub);

	} // end try
	catch (ClassNotFoundException cnfe)
	{ // class not found - bail out
	  throw new ConfigException("object handler class \"" + sub.getAttribute("class")
				    + "\" not found",sub);

	} // end catch

      } // end if
      else  // no referent for object handler - bail out now
	throw new ConfigException("object handler for method \"" + "\" not valid",sub);

    } // end if
    else if ((sub = cfg_h.getSubElement("script"))!=null)
    { // we're trying to use a script - make sure it has a valid attribute
      DOMElementHelper sub_h = new DOMElementHelper(sub);
      if (sub_h.hasAttribute("name"))
      { // valid namwe - now verify that the script exists
	script_name = sub.getAttribute("name");
	File test = new File(root.getRPCScriptPath(script_name));
	if (!(test.canRead()))
	  throw new ConfigException("object handler script \"" + script_name
				    + "\" is not a valid script file",sub);

      } // end if
      else  // no referent for object handler - bail out now
	throw new ConfigException("object handler for method \"" + "\" not valid",sub);

    } // end else if
    else  // no handler - this is an error
      throw new ConfigException("method \"" + method_name + "\" does not have a valid handler",cfg);

    if ((sub = cfg_h.getSubElement("session"))!=null)
    { // find the session parameter index
      session_param = loader.configGetAttributeInt(sub,"param");
      if (session_param<0)
	throw new ConfigException("invalid session parameter index",sub);

    } // end if

    NodeList nl = cfg.getChildNodes();
    HashMap tmp_env = new HashMap();
    for (int i=0; i<nl.getLength(); i++)
    { // look for specific nodes
      Node n = nl.item(i);
      if (n.getNodeType()==Node.ELEMENT_NODE)
      { // process various elements
	if (n.getNodeName().equals("env"))
	  tmp_env.put(loader.configGetAttribute((Element)n,"name"),
		      loader.configGetAttribute((Element)n,"value"));

      } // end if

    } // end for

    if (tmp_env.isEmpty())
      raw_env = Collections.EMPTY_MAP;
    else
      raw_env = Collections.unmodifiableMap(tmp_env);

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private final void bindSession(RequestInput req, XmlRpcRequest xreq) throws XmlRpcFault
  {
    if (logger.isDebugEnabled())
      logger.debug("trying to bind parameter at index " + session_param + " to session");
    if (session_param>=xreq.getParamCount())
    { // too few parameters - bail out!
      logger.error("session parameter is at index " + session_param + " but only " + xreq.getParamCount()
		   + " parameter(s) present");
      throw new XmlRpcFault(XmlRpcFault.SERVER_ERROR,"invalid parameter index for session binding");

    } // end if

    String session_id = xreq.getParamString(session_param).trim();
    if (logger.isDebugEnabled())
      logger.debug("Passed session ID = " + session_id);
    VeniceUISession vuis = RpcSessionBroker.get(req).getSession(session_id);
    if (vuis==null)
    { // the session cannot be acquired
      logger.debug("Cannot get session for session ID " + session_id);
      throw new XmlRpcFault(XmlRpcFault.INVALID_PARAMS,"session not found");

    } // end if

    SessionControl sctl = (SessionControl)(req.queryService(SessionControl.class));
    sctl.pushSession(vuis);  // push the session into the request

  } // end bindSession

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  public final void configureMatcher(RE re)
  {
    re.setProgram(method_pgm);

  } // end configureMatcher

  public final String getRemap()
  {
    return remap_value;

  } // end getRemap

  public final Object dispatch(RequestInput req, XmlRpcRequest xreq, Map sub_map) throws Exception, XmlRpcFault
  {
    // prepare the environment
    HashMap env = new HashMap();
    Iterator it = raw_env.entrySet().iterator();
    while (it.hasNext())
    { // do replacements to make the environment
      Map.Entry ntry = (Map.Entry)(it.next());
      env.put(ntry.getKey().toString(),StringUtil.replaceAllVariables(ntry.getValue().toString(),sub_map));

    } // end while

    if (obj_class!=null)
    { // object class method call - look for a cached object first
      Map cache = (Map)(req.getAppAttribute(XmlRpcServlet.OBJECT_CLASS_CACHE_ATTR));
      XmlRpcDispatch disp = (XmlRpcDispatch)(cache.get(obj_class.getName()));
      if (disp==null)
      { // create new instance and cache it
	disp = (XmlRpcDispatch)(obj_class.newInstance());
	cache.put(obj_class.getName(),disp);

      } // end if

      if (session_param>=0)
	bindSession(req,xreq);

      try
      { // dispatch the call
	return disp.dispatch(req,xreq,env);

      } // end try
      finally
      { // pop the session we pushed
	if (session_param>=0)
	{ // pop the session
	  SessionControl sctl = (SessionControl)(req.queryService(SessionControl.class));
	  sctl.popSession();

	} // end if

      } // end finally

    } // end if

    if (script_name!=null)
    { // script method call - prepare the script and execute it
      ScriptSupport ssup = (ScriptSupport)(req.queryService(ScriptSupport.class));
      String full_name = ssup.getRPCScriptName(script_name);
      String logger_name = ssup.getRPCScriptLoggerName(script_name);
      if (logger.isDebugEnabled())
	logger.debug("EXECUTING " + full_name);
      try
      { // execute the script!
	if (session_param>=0)
	  bindSession(req,xreq);

	ScriptManager smgr = (ScriptManager)(req.queryService(ScriptManager.class));
	smgr.pushContext();
	smgr.register("xmlrpc",xreq);
	smgr.register("environment",env);
	try
	{ // execute the script
	  ScriptReturn sro = new ScriptReturn();
	  smgr.exec(new File(full_name),logger_name,sro);
	  return sro.get();

	} // end try
	finally
	{ // make sure and pop the context before we return
	  smgr.popContext();
	  if (session_param>=0)
	  { // pop the session
	    SessionControl sctl = (SessionControl)(req.queryService(SessionControl.class));
	    sctl.popSession();

	  } // end if

	} // end finally

      } // end try
      catch (ScriptingException se)
      { // script error!  we are not amused...
	Throwable t = se.getException();
	if (t instanceof SupplyFaultCode)
	  return new XmlRpcFault(t);
	else
	  return new XmlRpcFault(XmlRpcFault.APPLICATION_ERROR,"scripting error: " + se.toString());

      } // end catch
      catch (ThrowableContent tc)
      { // this could be an XmlRpcFault, but maybe not
	if (tc instanceof XmlRpcFault)
	  return tc;
	else
	  return new XmlRpcFault(XmlRpcFault.APPLICATION_ERROR,"application error: " + tc.toString());

      } // end catch

    } // end if

    // unable to dispatch things
    return new XmlRpcFault(XmlRpcFault.APPLICATION_ERROR,"dispatch failure");

  } // end dispatch

} // end class XmlRpcMethod
