unit webjsonrpc;

{$mode objfpc}{$H+}
{ $define debugjsonrpc}

interface

uses
  Classes, SysUtils, fpjson, fpjsonrpc, httpdefs, fphttp, jsonparser;

Type
{ ---------------------------------------------------------------------
  HTTP handling and content producing methods
  ---------------------------------------------------------------------}

  { TCustomJSONRPCContentProducer }

  TCustomJSONRPCContentProducer = Class(THTTPContentProducer)
  Protected
    Function GetIDProperty : String; virtual;
    Procedure DoGetContent(ARequest : TRequest; Content : TStream; Var Handled : Boolean); override;
    Function GetDispatcher : TCustomJSONRPCDispatcher; virtual; abstract;
  end;

  { TJSONRPCContentProducer }

  TJSONRPCContentProducer = Class(TCustomJSONRPCContentProducer)
  private
    FDispatcher: TCustomJSONRPCDispatcher;
    procedure SetDispatcher(const AValue: TCustomJSONRPCDispatcher);
  Protected
    Function GetDispatcher : TCustomJSONRPCDispatcher; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation);override;
  Published
    Property Dispatcher :  TCustomJSONRPCDispatcher Read FDispatcher Write SetDispatcher;
  end;


  { TJSONRPCSessionContext }

  TJSONRPCSessionContext = Class(TJSONRPCCallContext)
  private
    FSession: TCustomSession;
  Public
    Constructor CreateSession(ASession : TCustomSession);
    Property Session : TCustomSession Read FSession;
  end;

  { TSessionJSONRPCDispatcher }

  TSessionJSONRPCDispatcher = Class(TCustomJSONRPCDispatcher)
  Protected
    Function FindHandler(Const AClassName,AMethodName : TJSONStringType;AContext : TJSONRPCCallContext; Out FreeObject : TComponent) : TCustomJSONRPCHandler; override;
  Published
    Property OnStartBatch;
    Property OnDispatchRequest;
    Property OnFindHandler;
    Property OnEndBatch;
    Property Options;
  end;

  { TJSONRPCDispatchModule }

  TJSONRPCDispatchModule = Class(TSessionHTTPModule)
  protected
    Function CreateContext : TJSONRPCSessionContext;
    Function DispatchRequest(Const ARequest : TRequest; ADispatcher : TCustomJSONRPCDispatcher) : TJSONData;
  end;

  { TCustomJSONRPCModule }

  TCustomJSONRPCModule = Class(TJSONRPCDispatchModule)
  private
    FDispatcher: TCustomJSONRPCDispatcher;
    FOptions: TJSONRPCDispatchOptions;
    FRequest: TRequest;
    FResponse: TResponse;
    procedure SetDispatcher(const AValue: TCustomJSONRPCDispatcher);
  Protected
    Function CreateDispatcher : TCustomJSONRPCDispatcher; virtual;
    procedure Notification(AComponent: TComponent; Operation: TOperation);override;
    Property Dispatcher :  TCustomJSONRPCDispatcher Read FDispatcher Write SetDispatcher;
    Property DispatchOptions : TJSONRPCDispatchOptions Read FOptions Write FOptions default DefaultDispatchOptions;
  Public
    Constructor CreateNew(AOwner : TComponent; CreateMode : Integer); override;
    Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); override;
    // Access to request
    Property Request: TRequest Read FRequest;
    // Access to response
    Property Response: TResponse Read FResponse;
  end;

  { TJSONRPCDataModule }

  TJSONRPCModule = Class(TCustomJSONRPCModule)
  Published
    Property Dispatcher;
    Property DispatchOptions;
  end;

implementation

{$ifdef debugjsonrpc}
uses dbugintf;
{$endif}

{ TCustomJSONRPCContentProducer }

function TCustomJSONRPCContentProducer.GetIDProperty: String;
begin
  Result:='id';
end;


procedure TCustomJSONRPCContentProducer.DoGetContent(ARequest: TRequest;
  Content: TStream; var Handled: Boolean);

Var
  Disp : TCustomJSONRPCDispatcher;
  P : TJSONParser;
  Req,res : TJSONData;
  R : String;

begin
  Disp:=Self.GetDispatcher;
  P:= TJSONParser.Create(ARequest.Content);
  try
    Res:=Nil;
    Req:=Nil;
    try
      try
        Req:=P.Parse;
        If (Disp<>Nil) then
          Res:=Disp.Execute(Req,Nil)
        else // No dispatcher, create error(s)
          Res:=CreateErrorForRequest(Req,CreateJSON2ErrorResponse(SErrNoDispatcher,EJSONRPCInternalError,Nil,GetIDProperty));
      except
        On E : Exception Do
          begin
          Res:=CreateJSON2ErrorResponse(E.Message,EJSONRPCParseError,Nil,GetIDProperty);
          end;
      end;
      try
        If Assigned(Res) then
          begin
          R:=Res.AsJSON;
          Content.WriteBuffer(R[1],Length(R));
          end;
        Handled:=True;
      finally
        FreeAndNil(Res);
      end;
    finally
      Req.Free;
    end;
  finally
    P.Free;
  end;
end;

{ TJSONRPCContentProducer }

procedure TJSONRPCContentProducer.SetDispatcher(
  const AValue: TCustomJSONRPCDispatcher);
begin
  if FDispatcher=AValue then exit;
  If Assigned(FDispatcher) then
    FDispatcher.RemoveFreeNotification(Self);
  FDispatcher:=AValue;
  If Assigned(FDispatcher) then
    FDispatcher.FreeNotification(Self);
end;

function TJSONRPCContentProducer.GetDispatcher: TCustomJSONRPCDispatcher;
begin
  Result:=FDispatcher;
end;

procedure TJSONRPCContentProducer.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  If (Operation=opRemove) and (AComponent=FDispatcher) then
    FDispatcher:=Nil;
end;

{ TCustomJSONRPCModule }

procedure TCustomJSONRPCModule.SetDispatcher(
  const AValue: TCustomJSONRPCDispatcher);
begin
  if FDispatcher=AValue then exit;
  If Assigned(FDispatcher) then
    FDispatcher.RemoveFreeNotification(Self);
  FDispatcher:=AValue;
  If Assigned(FDispatcher) then
    FDispatcher.FreeNotification(Self);
end;

function TCustomJSONRPCModule.CreateDispatcher: TCustomJSONRPCDispatcher;

Var
  S : TSessionJSONRPCDispatcher;

begin
  S:=TSessionJSONRPCDispatcher.Create(Self);
  S.Options:=DispatchOptions;
  Result:=S;
end;

procedure TCustomJSONRPCModule.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  If (Operation=opRemove) and (AComponent=FDispatcher) then
    FDispatcher:=Nil;
end;

constructor TCustomJSONRPCModule.CreateNew(AOwner: TComponent;
  CreateMode: Integer);
begin
  inherited CreateNew(AOwner, CreateMode);
  FOptions:=DefaultDispatchOptions+[jdoSearchRegistry];
end;



procedure TCustomJSONRPCModule.HandleRequest(ARequest: TRequest;
  AResponse: TResponse);

Var
  Disp : TCustomJSONRPCDispatcher;
  res : TJSONData;

begin
  If (Dispatcher=Nil) then
    Dispatcher:=CreateDispatcher;
  Disp:=Dispatcher;
  Res:=DispatchRequest(ARequest,Disp);
  try
    If Assigned(Res) then
      begin
      AResponse.Content:=Res.AsJSON;
      AResponse.ContentLength:=Length(AResponse.Content);
      end;
  AResponse.SendResponse;
  finally
    Res.Free;
  end;
end;

{ TJSONRPCSessionContext }

constructor TJSONRPCSessionContext.CreateSession(ASession: TCustomSession);
begin
  FSession:=ASession;
end;

{ TJSONRPCDispatchModule }

function TJSONRPCDispatchModule.CreateContext: TJSONRPCSessionContext;
begin
  If CreateSession then
    Result:=TJSONRPCSessionContext.CreateSession(Session)
  else
    Result:=TJSONRPCSessionContext.CreateSession(Nil);
end;

Function TJSONRPCDispatchModule.DispatchRequest(const ARequest: TRequest;
  ADispatcher: TCustomJSONRPCDispatcher): TJSONData;
var
  P : TJSONParser;
  Req : TJSONData;
  C : TJSONRPCSessionContext;


begin
  P:= TJSONParser.Create(ARequest.Content);
  try
    Result:=Nil;
    Req:=Nil;
    try
      try
        Req:=P.Parse;
        C:=CreateContext;
        try
         {$ifdef debugjsonrpc}SendDebugFmt('Dispatching request : "%s"',[Req.AsJSON]);{$endif}
          Result:=ADispatcher.Execute(Req,C);
        finally
          C.Free;
        end;
      except
        On E : Exception Do
          Result:=CreateJSON2ErrorResponse(E.Message,EJSONRPCParseError,Nil,ADispatcher.TransactionProperty);
      end;
    finally
      Req.Free;
    end;
  finally
    P.Free;
  end;
end;

{ TSessionJSONRPCDispatcher }

function TSessionJSONRPCDispatcher.FindHandler(const AClassName,
  AMethodName: TJSONStringType; AContext: TJSONRPCCallContext; out
  FreeObject: TComponent): TCustomJSONRPCHandler;
begin
  Result:=Inherited FindHandler(AClassName,AMethodName,AContext,FreeObject);
  If (AContext is TJSONRPCSessionContext) and (FreeObject is TCustomJSONRPCModule) then
    TCustomJSONRPCModule(FreeObject).Session:=TJSONRPCSessionContext(AContext).Session;
end;

end.

