mORMot and Open Source friends
Check-in [e2623df46f]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:TServiceFactoryServerInstance will now create instances server-side with a RefCount=1, to allow passing self as an interface in sub-methods - see http://synopse.info/forum/viewtopic.php?id=961
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: e2623df46f390a55982ff9ad4e0471ad0fe051e9
User & Date: abouchez 2012-12-06 13:20:29
Context
2012-12-06
13:21
included testing of interface based services in sicSingle mode check-in: 60ef888031 user: abouchez tags: trunk
13:20
TServiceFactoryServerInstance will now create instances server-side with a RefCount=1, to allow passing self as an interface in sub-methods - see http://synopse.info/forum/viewtopic.php?id=961 check-in: e2623df46f user: abouchez tags: trunk
12:25
added Owner, Actions and Events parameters to TSQLModel.Create() overloaded constructor check-in: 9b4ab9f114 user: abouchez tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMot.pas.

695
696
697
698
699
700
701


702
703
704
705
706
707
708
....
6184
6185
6186
6187
6188
6189
6190
6191
6192


6193
6194
6195
6196
6197
6198
6199
....
6249
6250
6251
6252
6253
6254
6255


6256
6257
6258
6259
6260
6261
6262
6263
.....
29223
29224
29225
29226
29227
29228
29229
29230
29231
29232
29233
29234
29235
29236
29237
.....
29248
29249
29250
29251
29252
29253
29254
29255
29256
29257
29258
29259
29260
29261
29262
.....
29268
29269
29270
29271
29272
29273
29274
29275
29276
29277
29278
29279
29280
29281
29282
.....
29283
29284
29285
29286
29287
29288
29289

29290
29291
29292
29293
29294
29295
29296
29297
29298
29299
29300
29301
29302
29303
29304
29305
29306
29307
29308
29309
29310
29311
29312
.....
29360
29361
29362
29363
29364
29365
29366
29367
29368
29369
29370
29371


29372
29373
29374
29375
29376
29377
29378
.....
29386
29387
29388
29389
29390
29391
29392
29393
29394
29395
29396
29397
29398
29399
29400
.....
29454
29455
29456
29457
29458
29459
29460
29461
29462
29463
29464
29465
29466
29467
29468
    - new TInterfaceFactory.CreateFakeInstance() method for easy mocking of
      any interface, via some event callbacks
    - added TServiceMethod.DefaultResult property, to be used for stubs/mocks
    - TServiceFactory.Create() and its children will now always have an optional
      aContractExpected parameter (for consistency with TServiceFactoryClient)
    - introduce smvVariant kind of parameter for interface-based services
    - safer TInterfacedObjectFake.FakeCall() stack use


    - huge code refactoring of the ORM model implementation: a new dedicated
      TSQLModelRecordProperties will contain per-TSQLModel parameters, whereas
      shared information retrieved by RTTI remain in TSQLRecordProperties - this
      will allow use of the same TSQLRecord in several TSQLModel at once, with
      dedicated SQL auto-generation and external DB settings
    - added aExternalTableName/Database optional parameters to method
      TSQLModel.VirtualTableRegister()
................................................................................
  public
    /// the internal Instance ID, as remotely sent in "id":1
    // - is set to 0 when an entry in the array is free
    InstanceID: Cardinal;
    /// last time stamp access of this instance
    LastAccess: Cardinal;
    /// the implementation instance itself
    Instance: TObject;
    /// used to catch and ignore any exception in Instance.Free


    procedure SafeFreeInstance;
  end;

  /// server-side service provider uses this to store its internal instances
  // - used by TServiceFactoryServer in sicClientDriven, sicPerSession,
  // sicPerUser or sicPerGroup mode
  TServiceFactoryServerInstanceDynArray = array of TServiceFactoryServerInstance;
................................................................................
    // for instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    procedure ExecuteMethod(var Ctxt: TSQLRestServerCallBackParams;
      aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);
    /// this method will create an implementation instance


    function CreateInstance: TInterfacedObject;
  public
    /// initialize the service provider on the server side
    // - expect an direct server-side implementation class (inheriting from
    // TInterfacedClass or from TInterfacedObjectWithCustomCreate if you need
    // an overriden constructor)
    // - for sicClientDriven, sicPerSession, sicPerUser or sicPerGroup modes,
    // a time out (in seconds) can be defined - if the time out is 0, interface
................................................................................
      [InterfaceURI,fRest.ClassName]) else
  fImplementationClass := aImplementationClass;
  if fImplementationClass.InheritsFrom(TInterfacedObjectWithCustomCreate) then
    fImplementationClassWithCustomCreate := true;
  // initialize the shared instance or client driven parameters
  case InstanceCreation of
  sicShared: begin
    fSharedInstance := CreateInstance;
    if (fSharedInstance=nil) or
       not fSharedInstance.GetInterface(fInterface.fInterfaceIID,fSharedInterface) then
      raise EServiceException.CreateFmt('No implementation available for "%s" interface',
        [fInterfaceURI]);
  end;
  sicClientDriven, sicPerSession, sicPerUser, sicPerGroup:
    if aTimeOutSec=0 then
................................................................................

destructor TServiceFactoryServer.Destroy;
var i: integer;
begin
  try // release any internal instance (should have been done by client)
    for i := 0 to fInstancesCount-1 do
      if fInstances[i].Instance<>nil then
        fInstances[i].Instance.Free;
  except
    ; // better ignore any error in business logic code
  end;
  DeleteCriticalSection(fInstanceLock);
  inherited;
end;

................................................................................
    case fInstanceCreation of
    sicShared:
    if fSharedInterface<>nil then begin
      IInterface(Obj) := fSharedInterface; // copy implementation interface
      result := true;
    end;
    else begin
      O := CreateInstance;
      if O<>nil then
        result := O.GetInterface(fInterface.fInterfaceIID,Obj);
    end;
    end;
end;

function TServiceFactoryServer.RetrieveSignature: RawUTF8;
................................................................................
begin
  if self=nil then
    result := '' else
    result := Contract; // just return the current value
end;

procedure TServiceFactoryServerInstance.SafeFreeInstance;

begin
  try
    InstanceID := 0;
    FreeAndNil(Instance);
  except
    ; // just ignore any error in customer code
  end;
end;

function TServiceFactoryServer.InternalInstanceRetrieve(
  var Inst: TServiceFactoryServerInstance; aMethodIndex: integer): boolean;
procedure AddNew;
var i: integer;
    P: ^TServiceFactoryServerInstance;
begin
  Inst.Instance := CreateInstance;
  if Inst.Instance=nil then
    exit;
  P := pointer(fInstances);
  for i := 1 to fInstancesCount do
    if P^.InstanceID=0 then begin
      P^ := Inst; // found an empty entry -> use it
      exit;
................................................................................
end;

function TServiceFactoryServer.RestServer: TSQLRestServer;
begin
  result := TSQLRestServer(Rest);
end;

function TServiceFactoryServer.CreateInstance: TInterfacedObject;
begin
  if fImplementationClassWithCustomCreate then
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create else
    result := fImplementationClass.Create;


end;

procedure TServiceFactoryServer.ExecuteMethod(
  var Ctxt: TSQLRestServerCallBackParams;
  aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);
procedure Error(Msg: PUTF8Char);
begin
................................................................................
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := CreateInstance;
    sicShared:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven, sicPerSession, sicPerUser, sicPerGroup: begin
      if InstanceCreation=sicClientDriven then
        Inst.InstanceID := aInstanceID else
................................................................................
        Factory := nil;
        Session := nil;
      end;
      WR.Free;
    end;
  finally
    if InstanceCreation=sicSingle then
      Inst.Instance.Free; // always release single shot instance
  end;
end;

function TServiceFactoryServer.AllowAll: TServiceFactoryServer;
var m: integer;
begin
  if self<>nil then






>
>







 







|
|
>
>







 







>
>
|







 







|







 







|







 







|







 







>

<
|
|
|
|
<








|







 







|




>
>







 







|







 







|







695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
....
6186
6187
6188
6189
6190
6191
6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
....
6253
6254
6255
6256
6257
6258
6259
6260
6261
6262
6263
6264
6265
6266
6267
6268
6269
.....
29229
29230
29231
29232
29233
29234
29235
29236
29237
29238
29239
29240
29241
29242
29243
.....
29254
29255
29256
29257
29258
29259
29260
29261
29262
29263
29264
29265
29266
29267
29268
.....
29274
29275
29276
29277
29278
29279
29280
29281
29282
29283
29284
29285
29286
29287
29288
.....
29289
29290
29291
29292
29293
29294
29295
29296
29297

29298
29299
29300
29301

29302
29303
29304
29305
29306
29307
29308
29309
29310
29311
29312
29313
29314
29315
29316
29317
.....
29365
29366
29367
29368
29369
29370
29371
29372
29373
29374
29375
29376
29377
29378
29379
29380
29381
29382
29383
29384
29385
.....
29393
29394
29395
29396
29397
29398
29399
29400
29401
29402
29403
29404
29405
29406
29407
.....
29461
29462
29463
29464
29465
29466
29467
29468
29469
29470
29471
29472
29473
29474
29475
    - new TInterfaceFactory.CreateFakeInstance() method for easy mocking of
      any interface, via some event callbacks
    - added TServiceMethod.DefaultResult property, to be used for stubs/mocks
    - TServiceFactory.Create() and its children will now always have an optional
      aContractExpected parameter (for consistency with TServiceFactoryClient)
    - introduce smvVariant kind of parameter for interface-based services
    - safer TInterfacedObjectFake.FakeCall() stack use
    - TServiceFactoryServerInstance will now create instances server-side
      with a RefCount=1, to allow passing self as an interface in sub-methods
    - huge code refactoring of the ORM model implementation: a new dedicated
      TSQLModelRecordProperties will contain per-TSQLModel parameters, whereas
      shared information retrieved by RTTI remain in TSQLRecordProperties - this
      will allow use of the same TSQLRecord in several TSQLModel at once, with
      dedicated SQL auto-generation and external DB settings
    - added aExternalTableName/Database optional parameters to method
      TSQLModel.VirtualTableRegister()
................................................................................
  public
    /// the internal Instance ID, as remotely sent in "id":1
    // - is set to 0 when an entry in the array is free
    InstanceID: Cardinal;
    /// last time stamp access of this instance
    LastAccess: Cardinal;
    /// the implementation instance itself
    Instance: TInterfacedObject;
    /// used to release the implementation instance
    // - direct FreeAndNil(Instance) may lead to A/V if self has been assigned
    // to an interface to any sub-method on the server side -> dec(RefCount)
    procedure SafeFreeInstance;
  end;

  /// server-side service provider uses this to store its internal instances
  // - used by TServiceFactoryServer in sicClientDriven, sicPerSession,
  // sicPerUser or sicPerGroup mode
  TServiceFactoryServerInstanceDynArray = array of TServiceFactoryServerInstance;
................................................................................
    // for instance, ExecuteMethod(..,'[1,2]') over ICalculator.Add will return:
    // $ {"result":[3],"id":0}
    // the returned "id" number is the Instance identifier to be used for any later
    // sicClientDriven remote call - or just 0 in case of sicSingle or sicShared
    procedure ExecuteMethod(var Ctxt: TSQLRestServerCallBackParams;
      aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);
    /// this method will create an implementation instance
    // - reference count will be set to one, in order to allow safe passing
    // of the instance into an interface, if AndIncreaseRefCount is TRUE
    function CreateInstance(AndIncreaseRefCount: boolean): TInterfacedObject;
  public
    /// initialize the service provider on the server side
    // - expect an direct server-side implementation class (inheriting from
    // TInterfacedClass or from TInterfacedObjectWithCustomCreate if you need
    // an overriden constructor)
    // - for sicClientDriven, sicPerSession, sicPerUser or sicPerGroup modes,
    // a time out (in seconds) can be defined - if the time out is 0, interface
................................................................................
      [InterfaceURI,fRest.ClassName]) else
  fImplementationClass := aImplementationClass;
  if fImplementationClass.InheritsFrom(TInterfacedObjectWithCustomCreate) then
    fImplementationClassWithCustomCreate := true;
  // initialize the shared instance or client driven parameters
  case InstanceCreation of
  sicShared: begin
    fSharedInstance := CreateInstance(false);
    if (fSharedInstance=nil) or
       not fSharedInstance.GetInterface(fInterface.fInterfaceIID,fSharedInterface) then
      raise EServiceException.CreateFmt('No implementation available for "%s" interface',
        [fInterfaceURI]);
  end;
  sicClientDriven, sicPerSession, sicPerUser, sicPerGroup:
    if aTimeOutSec=0 then
................................................................................

destructor TServiceFactoryServer.Destroy;
var i: integer;
begin
  try // release any internal instance (should have been done by client)
    for i := 0 to fInstancesCount-1 do
      if fInstances[i].Instance<>nil then
        fInstances[i].SafeFreeInstance; 
  except
    ; // better ignore any error in business logic code
  end;
  DeleteCriticalSection(fInstanceLock);
  inherited;
end;

................................................................................
    case fInstanceCreation of
    sicShared:
    if fSharedInterface<>nil then begin
      IInterface(Obj) := fSharedInterface; // copy implementation interface
      result := true;
    end;
    else begin
      O := CreateInstance(false);
      if O<>nil then
        result := O.GetInterface(fInterface.fInterfaceIID,Obj);
    end;
    end;
end;

function TServiceFactoryServer.RetrieveSignature: RawUTF8;
................................................................................
begin
  if self=nil then
    result := '' else
    result := Contract; // just return the current value
end;

procedure TServiceFactoryServerInstance.SafeFreeInstance;
var Obj: TInterfacedObject;
begin

  InstanceID := 0;
  Obj := Instance;
  Instance := nil;
  IInterface(Obj)._Release;

end;

function TServiceFactoryServer.InternalInstanceRetrieve(
  var Inst: TServiceFactoryServerInstance; aMethodIndex: integer): boolean;
procedure AddNew;
var i: integer;
    P: ^TServiceFactoryServerInstance;
begin
  Inst.Instance := CreateInstance(true);
  if Inst.Instance=nil then
    exit;
  P := pointer(fInstances);
  for i := 1 to fInstancesCount do
    if P^.InstanceID=0 then begin
      P^ := Inst; // found an empty entry -> use it
      exit;
................................................................................
end;

function TServiceFactoryServer.RestServer: TSQLRestServer;
begin
  result := TSQLRestServer(Rest);
end;

function TServiceFactoryServer.CreateInstance(AndIncreaseRefCount: boolean): TInterfacedObject;
begin
  if fImplementationClassWithCustomCreate then
    result := TInterfacedObjectWithCustomCreateClass(fImplementationClass).Create else
    result := fImplementationClass.Create;
  if AndIncreaseRefCount then
    IInterface(result)._AddRef; // allow passing self to sub-methods
end;

procedure TServiceFactoryServer.ExecuteMethod(
  var Ctxt: TSQLRestServerCallBackParams;
  aMethodIndex, aInstanceID: cardinal; aParamsJSONArray: PUTF8Char);
procedure Error(Msg: PUTF8Char);
begin
................................................................................
  // 1. initialize Inst.Instance and Inst.InstanceID
  Inst.InstanceID := 0;
  Inst.Instance := nil;
  case InstanceCreation of
    sicSingle:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := CreateInstance(true);
    sicShared:
      if aMethodIndex>=fInterface.fMethodsCount then
        exit else
        Inst.Instance := fSharedInstance;
    sicClientDriven, sicPerSession, sicPerUser, sicPerGroup: begin
      if InstanceCreation=sicClientDriven then
        Inst.InstanceID := aInstanceID else
................................................................................
        Factory := nil;
        Session := nil;
      end;
      WR.Free;
    end;
  finally
    if InstanceCreation=sicSingle then
      Inst.SafeFreeInstance; // always release single shot instance
  end;
end;

function TServiceFactoryServer.AllowAll: TServiceFactoryServer;
var m: integer;
begin
  if self<>nil then