#Include wconnect.h

DO wwJsonSerializer
SET PROCEDURE TO wwJsonService ADDITIVE

*************************************************************
DEFINE CLASS wwJsonService AS CUSTOM
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2006
*:Contact: http://www.west-wind.com
*:Created: 09/20/2006
*************************************************************

*** Custom Properties

*** Comma delimited list of methods that are allowed to be
*** called on the Target object
AllowedMethods = ""

*** Allows the method called to specify not to serialize 
*** the result from the method. Use this if your service
*** method itself calls Response.Write or otherwise outputs
*** the final response into the Response object.
IsRawResponse = .F.

*** Flag set when an error occurs during processing
*** When set usually you'll want to set the status to 500
ErrorOccurred = .F.

*** Error Message of last error that occured
ErrorMessage = ""

*** Last Exception that happened during request
LastException = null

*** When .t. allows errors to stop rather than fire
IsDebugMode = .F.

*** Default Response Mode is JSON
ResponseMode = "json"   && "xml"


************************************************************************
* wwJsonService ::  CallMethod
****************************************
***  Function: Responsible for actually executing a method on the page
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION CallMethod(Request,loTargetObject, lcMethod)
LOCAL lnParms, lcMethod,loSerializer, lcValue, lnX, lcCallback, lcOutput
LOCAL lcAcceptType

this.IsRawResponse = .F.
this.ErrorOccurred = .F.
this.LastException = null
this.ErrorMessage = ""

loSerializer = CREATEOBJECT("wwJSONSerializer")
loSerializer.FormattedOutput = this.IsDebugMode 

*** Create private JsonService and Serializer instances
*** that are passed down the call stack to be accessed
*** from within the actual methods called
PRIVATE JsonService, Serializer
JsonService = THIS
Serializer = loSerializer

*** We check for JSON client request data
lcContentType =  LOWER(Request.ServerVariables("CONTENT_TYPE"))
lcAcceptType = LOWER(Request.ServerVariables("HTTP_ACCEPT"))

THIS.ResponseMode = "json"
IF !EMPTY(lcAcceptType) AND lcAcceptType = "text/xml"
   THIS.ResponseMode = "xml"
ENDIF

IF EMPTY(lcMethod)
  lcMethod=Request.Params("CallbackMethod")
  IF EMPTY(lcMethod)
    lcMethod=Request.Params("Method")
  ENDIF
ENDIF  

DO CASE

*** If JSON was passed as input use it as raw input for a single parameter   
CASE lcContentType = "application/x-javascript" OR ;
     lcContentType = "application/json"
   IF EMPTY(lcMethod)
	   	RETURN this.ReturnExceptionObject("No method specified")
   ENDIF	   	
      
   *** If raw JSON is passed we have a single JSON parameter
   lcParms = Request.GetRawFormData()

   *** Parse the parameter into a PRIVATE variable
   IF !EMPTY(lcParms)
   		*** Create a PRIVATE variable on the fly
   		PRIVATE __JsonParm, lcParm
		__JsonParm = loSerializer.DeserializeJson(lcParms)
		lcParms = "__JsonParm"    			
   ENDIF     

*** Assume it's a post from the AjaxMethodCallback client component   
CASE !EMPTY(lcMethod) AND ;
     Request.IsFormVar("CallbackParmCount")      
     
	lcParmCount = Request.Params("CallbackParmCount")

	IF VARTYPE(Request.lUtf8Encoding)="L"
		Request.lUtf8Encoding = .T.
	ENDIF
		
	lnParms = VAL(lcParmCount)

	*** Ensure that the method is in the allowed list
	*** Note if the list is empty all methods are allowed
	IF !EMPTY(this.AllowedMethods) AND ;
	   ATC("," + lcMethod + "," , "," + this.AllowedMethods + ",") = 0
		RETURN this.ReturnExceptionObject("Method access denied: "  + lcMethod)
	ENDIF

	lcParms = ""

	FOR lnX = 1 TO lnParms
		lcParm = "Parm" + TRANSFORM(lnX)
		lcValue = Request.Params( lcParm )
		&lcParm = loSerializer.Deserialize(lcValue)
		lcParms = lcParms + lcParm  + ","  
		*** &&     "STRCONV(" + lcParm + ",11),"
	ENDFOR

	#IF wwVFPVersion > 8
		lcParms = RTRIM(lcParms,0,",")
	#ELSE
		lcParms = TrimWhiteSpace(lcParms," ,")
	#ENDIF	
CASE !EMPTY(lcMethod) AND ;
     !EMPTY(Request.QueryString("CallbackParmCount"))
     
	lcParmCount = Request.Params("CallbackParmCount")
	lnParms = VAL(lcParmCount)

	*** Ensure that the method is in the allowed list
	*** Note if the list is empty all methods are allowed
	IF !EMPTY(this.AllowedMethods) AND ;
	   ATC("," + lcMethod + "," , "," + this.AllowedMethods + ",") = 0
		RETURN this.ReturnExceptionObject("Method access denied: "  + lcMethod)
	ENDIF

	lcParms = ""

	FOR lnX = 1 TO lnParms
		lcParm = "Parm" + TRANSFORM(lnX)
		lcValue = Request.Params( lcParm )
		&lcParm = lcValue
		lcParms = lcParms + lcParm  + ","  
	ENDFOR

	#IF wwVFPVersion > 8
		lcParms = RTRIM(lcParms,0,",")
	#ELSE
		lcParms = TrimWhiteSpace(lcParms," ,")
	#ENDIF	

*** No content type check for method parameter
*** Just call the method without parameters
OTHERWISE
   IF EMPTY(lcMethod)
      lcMethod  = Request.Params("Method")
   ENDIF
   IF EMPTY(lcMethod)
	   	RETURN this.ReturnExceptionObject("No method specified")
   ENDIF	    
   lcParms = ""	
ENDCASE

LOCAL llError, loError, lcCmd, loException, lcOutput
llError = .F.
loException = null

IF this.IsDebugMode
	lcCmd = "loTargetObject." + lcMethod + "(" + lcParms + ")"
	lcResult = EVALUATE( lcCmd )
ELSE
	TRY
		lcCmd = "loTargetObject." + lcMethod + "(" + lcParms + ")"
		lcResult = EVALUATE( lcCmd )
	CATCH TO loException
	    llError = .T.
		THIS.OnError(loException)
	ENDTRY
ENDIF

IF llError
	RETURN this.ReturnExceptionObject(loException.Message)
ENDIF	

*** Response is handled by method
IF this.IsRawResponse
   RETURN ""
ENDIF

*** Non-JSON data expected - don't serialize
IF VARTYPE(Response) = "O" AND ;
   Response.ContentType != "application/json" AND; 
   Response.ContentType != "application/x-javascript" AND;
   Response.ContentType != "text/"
   
   IF ISNULL(lcResult)
      lcResult = ""
   ENDIF
   
   RETURN lcResult
ENDIF

*** Handle JSONP response
lcCallback = Request.QueryString("callback")

IF THIS.ResponseMode = "xml"
	RETURN this.ReturnXmlREsponse(lcResult)
ELSE	
	lcOutput = 	loSerializer.Serialize(lcResult)
ENDIF

*** JSON result
IF EMPTY(lcCallback)
	RETURN lcOutput
ENDIF

*** JSONP
RETURN lcCallback + "( " + lcOutput + " )"
ENDFUNC
*  wwJsonService ::  CallPageMethod

************************************************************************
* wwJsonService ::  OnError
****************************************
***  Function: Method called when an error occurs during Callback
***            method execution.
***    Assume: Hook up a handler with 
***            BINDEVENT(loService,"OnError",loObject,"OnCallbackError")
***      Pass: loException - the exception that occurred
***    Return: nothing
************************************************************************
FUNCTION OnError(loException)

this.ErrorOccurred = .T.
IF VARTYPE(loException) = "O"
	this.ErrorMessage = loException.Message
	this.LastException = loException
ELSE
	this.ErrorMessage = "Unknown error."
	this.LastException = CREATEOBJECT("Exception")
	this.LastException.Message = this.ErrorMessage
ENDIF	

ENDFUNC
*  wwMethodCallback ::  OnError


************************************************************************
* wwJsonService ::  ReturnXmlResponse
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION ReturnXmlResponse(loValue)
LOCAL loXml
loXml = CREATEOBJECT("wwXml")
loXml.lRecurseObjects = .T.
RETURN loXml.ObjectToXml(loValue,"doc")
ENDFUNC
*  wwJsonService ::  ReturnXmlResponse

************************************************************************
* wwJsonService ::  ReturnExceptionObject
********************************************
***  Function: Generic way to return an exception
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION ReturnExceptionObject(lcMessage)
LOCAL loError

this.ErrorOccurred = .T.
this.ErrorMessage = lcMessage

IF VARTYPE(this.LastException) # "O"
   this.LastException = CREATEOBJECT("Exception")
   this.LastException.Message = lcMessage
ENDIF

IF (this.ResponseMode = "xml")
   Response.ContentType = "text/xml"
   RETURN [<doc><isCallbackError>1</isCallbackError><message>] + lcMessage + [</message></doc>]
ENDIF

RETURN [{"isCallbackError":true,"message": ] + JsonString(lcMessage)  + [ }]
ENDFUNC

ENDDEFINE