/*
 * 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 javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.apache.regexp.*;
import org.w3c.dom.Document;
import com.silverwrist.util.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.RootConfig;
import com.silverwrist.venice.ui.servlet.BaseServlet;
import com.silverwrist.venice.ui.servlet.RequestImpl;

public class XmlRpcServlet extends BaseServlet
{
  /*--------------------------------------------------------------------------------
   * Inner class that implements the actual "normal" return value
   *--------------------------------------------------------------------------------
   */

  static class XmlRpcReturnValue implements ContentExecute
  {
    private Object obj;

    XmlRpcReturnValue(Object obj)
    {
      this.obj = obj;

    } // end constructor

    public void execute(RequestExec req) throws IOException
    {
      StringBuffer b =
	  new StringBuffer("<?xml version=\"1.0\"?>\r\n<methodResponse><params><param>\r\n<value>");
      try
      { // serialize the returned object
	XmlRpcSerializer serializer = XmlRpcSerializer.get();
	b.append(serializer.serialize(obj));

      } // end try
      catch (XmlRpcFault f)
      { // if we get a fault here, just execute THAT instead
	f.execute(req);
	return;

      } // end catch

      b.append("</value>\r\n</param></params></methodResponse>\r\n");
      byte[] raw = b.toString().getBytes("UTF-8");
      req.sendBinary("text/xml",raw);

    } // end execute

  } // end class XmlRpcReturnValue

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

  private static final String METHOD_LIST_ATTR = "ui.xmlrpc.MethodList";

  public static final String OBJECT_CLASS_CACHE_ATTR = "ui.xmlrpc.class.ObjectCache";

  /*--------------------------------------------------------------------------------
   * Overrides from class BaseServlet
   *--------------------------------------------------------------------------------
   */

  protected void init(ServletConfig config, ServletContext ctxt, VeniceEngine engine, RootConfig root,
		      String root_file_path)
  {
    List methods = root.getXmlRpcMethods();
    RequestImpl.setAppAttribute(ctxt,METHOD_LIST_ATTR,methods);
    RequestImpl.setAppAttribute(ctxt,OBJECT_CLASS_CACHE_ATTR,Collections.synchronizedMap(new HashMap()));
    RpcSessionBroker.init(ctxt,engine,root);

  } // end init

  protected VeniceUISessionFactory getSessionFactory()
  {
    return NullSession.get();  // null session by default

  } // end getSessionFactory

  protected Object translateServletException(RequestInput ri, Exception e)
  {
    return null;

  } // end translateServletException

  public Object process(RequestInput req)
  {
    XmlRpcRequest xreq;
    try
    { // get the XML-RPC request structure
      Document req_doc = (Document)(req.queryService(Document.class,"requestDocument"));
      xreq = new XmlRpcRequest(req,req_doc);

    } // end try
    catch (XmlRpcFault f)
    { // return the fault structure
      return f;

    } // end catch

    // dispatch the call and wrap non-fault returns for proper XML formatting
    Object rc = dispatchMethodCall(req,xreq);
    if (!(rc instanceof XmlRpcFault))
      rc = new XmlRpcReturnValue(rc);
    return rc;

  } // end process

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

  static Object dispatchMethodCall(RequestInput req, XmlRpcRequest xreq)
  {
    if (logger.isDebugEnabled())
      logger.debug("XML-RPC Method Call: " + xreq.getMethod());

    // Prepare the parameters map.
    int i;
    HashMap param_repl_map = new HashMap();
    for (i=0; i<xreq.getParamCount(); i++)
    { // fill in the parameters...
      Object obj = xreq.getParam(i);
      if ((obj instanceof String) || (obj instanceof Integer) || (obj instanceof Double))
	param_repl_map.put("param." + i,obj.toString());

    } // end for

    // Find an appropriate method to handle this call.
    List methods = (List)(req.getAppAttribute(METHOD_LIST_ATTR));
    boolean rescan = false;
    RE matcher = new RE();
    String the_name = xreq.getMethod();
    XmlRpcMethod meth = null;
    HashMap sub_map;
    do
    { // search for a method
      rescan = false;
      Iterator it = methods.iterator();
      boolean found = false;
      while (!found && it.hasNext())
      { // look for a match on the method name
	meth = (XmlRpcMethod)(it.next());
	meth.configureMatcher(matcher);
	found = matcher.match(the_name);

      } // end while

      if (!found)
      { // method not found
	logger.error("Method not found: " + the_name);
	return new XmlRpcFault(XmlRpcFault.METHOD_NOT_FOUND,"Method not found: " + the_name);

      } // end if

      // prepare the substitution map
      sub_map = (HashMap)(param_repl_map.clone());
      int ct = matcher.getParenCount();
      for (i=0; i<=ct; i++)
	sub_map.put("match." + i,matcher.getParen(i));

      // test for method remapping
      String remap = meth.getRemap();
      if (remap!=null)
      { // substitute in variables and rescan
	the_name = StringUtil.replaceAllVariables(remap,sub_map);
	if (logger.isDebugEnabled())
	  logger.debug("-- call remapped to method: " + the_name);
	rescan = true;

      } // end if

    } while (rescan); // end do

    Object rc;
    try
    { // call the method and translate anything "out of the ordinary" into an XmlRpcFault
      rc = meth.dispatch(req,xreq,sub_map);
      if (rc==null)
	rc = new XmlRpcFault(XmlRpcFault.APPLICATION_ERROR,"no return value");
      else if (rc instanceof Exception)
	rc = new XmlRpcFault((Exception)rc);

    } // end try
    catch (Exception e)
    { // if there's an exception, turn it into a "Fault" message
      rc = new XmlRpcFault(e);

    } // end catch
    catch (XmlRpcFault f)
    { // pass faults right on up
      rc = f;

    } // end catch

    return rc;

  } // end dispatchMethodCall

} // end class XmlRpcServlet
