/*
 * pxmlrpcs.cxx
 *
 * XML/RPC support
 *
 * Portable Windows Library
 *
 * Copyright (c) 2002 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (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 Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Contributor(s): ______________________________________.
 *
 * $Revision: 20385 $
 * $Author: rjongbloed $
 * $Date: 2008-06-04 10:40:38 +0000 (Wed, 04 Jun 2008) $
 */

// This depends on the expat XML library by Jim Clark
// See http://www.jclark.com/xml/expat.html for more information

#include <ptlib.h>

#ifdef __GNUC__
#pragma implementation "pxmlrpcs.h"
#endif

#define   DEFAULT_XMPRPC_URL  "/RPC2"

#if P_XMLRPC

#include <ptclib/pxmlrpcs.h>

#define new PNEW


PXMLRPCServerResource::PXMLRPCServerResource()
  : PHTTPResource(DEFAULT_XMPRPC_URL)
{
}

PXMLRPCServerResource::PXMLRPCServerResource(
      const PHTTPAuthority & auth)    // Authorisation for the resource.
  : PHTTPResource(DEFAULT_XMPRPC_URL, auth)
{
}
PXMLRPCServerResource::PXMLRPCServerResource(
      const PURL & url)               // Name of the resource in URL space.
  : PHTTPResource(url)
{
}

PXMLRPCServerResource::PXMLRPCServerResource(
      const PURL & url,              // Name of the resource in URL space.
      const PHTTPAuthority & auth    // Authorisation for the resource.
    )
  : PHTTPResource(url, auth)
{
}

PBoolean PXMLRPCServerResource::SetMethod(const PString & methodName, const PNotifier & func)
{
  PWaitAndSignal m(methodMutex);

  // find the method, or create a new one
  PXMLRPCServerMethod * methodInfo;
  PINDEX pos = methodList.GetValuesIndex(methodName);
  if (pos != P_MAX_INDEX)
    methodInfo = (PXMLRPCServerMethod *)methodList.GetAt(pos);
  else {
    methodInfo = new PXMLRPCServerMethod(methodName);
    methodList.Append(methodInfo);
  }

  // set the function
  methodInfo->methodFunc = func;

  return PTrue;
}

PBoolean PXMLRPCServerResource::LoadHeaders(PHTTPRequest & /*request*/)    // Information on this request.
{
  return PTrue;
}

PBoolean PXMLRPCServerResource::OnPOSTData(PHTTPRequest & request,
                                const PStringToString & /*data*/)
{
  PString reply;

  OnXMLRPCRequest(request.entityBody, reply);

  PTRACE(4, "XMLRPC\tOnPOSTData() sending XML reply:" << reply);

  request.code = PHTTP::RequestOK;
  request.outMIME.SetAt(PHTTP::ContentTypeTag(), "text/xml");

  PINDEX len = reply.GetLength();
  request.server.StartResponse(request.code, request.outMIME, len);
  return request.server.Write((const char *)reply, len);
}


void PXMLRPCServerResource::OnXMLRPCRequest(const PString & body, PString & reply)
{
  // get body of message here
  PXMLRPCBlock request;
  PBoolean ok = request.Load(body);
  
  PTRACE(4, "XMLRPC\tOnXMLRPCRequest() received XML request:" << body);
  
  // if cannot parse XML, set return
  if (!ok) {
    reply = FormatFault(PXMLRPC::CannotParseRequestXML, "XML error:" + request.GetErrorString());
    return;
  }

  // make sure methodCall is specified as top level
  if ((request.GetDocumentType() != "methodCall") || (request.GetNumElements() < 1)) {
    reply = FormatFault(PXMLRPC::RequestHasWrongDocumentType, "document type is not methodCall");
    return;
  }

  // make sure methodName is speciified
  PXMLElement * methodName = request.GetElement("methodName");
  if (methodName == NULL) {
    reply = FormatFault(PXMLRPC::RequestHasNoMethodName, "methodCall has no methodName");
    return;
  }

  // extract method name
  if ((methodName->GetSize() != 1) || (methodName->GetElement(0)->IsElement())) {
    reply = FormatFault(PXMLRPC::MethodNameIsEmpty, "methodName is empty");
    return;
  }
  PString method = ((PXMLData *)methodName->GetElement(0))->GetString();

  // extract params
  PTRACE(3, "XMLRPC\tReceived XMLRPC request for method " << method);

  OnXMLRPCRequest(method, request, reply);
}

void PXMLRPCServerResource::OnXMLRPCRequest(const PString & methodName,
                                            PXMLRPCBlock & request,
                                            PString & reply)
{
  methodMutex.Wait();

  // find the method information
  PINDEX pos = methodList.GetValuesIndex(methodName);
  if (pos == P_MAX_INDEX) {
    reply = FormatFault(PXMLRPC::UnknownMethod, "unknown method " + methodName);
    methodMutex.Signal();
    return;
  }
  PXMLRPCServerMethod * methodInfo = (PXMLRPCServerMethod *)methodList.GetAt(pos);
  PNotifier notifier = methodInfo->methodFunc;
  methodMutex.Signal();

  // create paramaters
  PXMLRPCServerParms p(*this, request);

  // call the notifier
  notifier(p, 0);

  // get the reply
  if (request.GetFaultCode() != P_MAX_INDEX)
    reply = FormatFault(request.GetFaultCode(), request.GetFaultText());
  else {
    PStringStream r; r << p.response;
    reply = r;
  }
}


PString PXMLRPCServerResource::FormatFault(PINDEX code, const PString & str)
{
  PTRACE(2, "XMLRPC\trequest failed: " << str);

  PStringStream reply;
  reply << "<?xml version=\"1.0\"?>\n"
           "<methodResponse>"
             "<fault>"
               "<value>"
                 "<struct>"
                   "<member>"
                     "<name>faultCode</name>"
                     "<value><int>" << code << "</int></value>"
                   "</member>"
                   "<member>"
                     "<name>faultString</name>"
                     "<value><string>" << str << "</string></value>"
                   "</member>"
                 "</struct>"
               "</value>"
             "</fault>"
           "</methodResponse>";
  return reply;
}

#endif // P_XMLRPC

