Service ancestor can not (always) be found in RODL

After migrating to RO 10.0.0.1521 we have an issue with our dataabract (DA3) services.

Our client code searches for operations by name in the RODL library but now method ‘UpdateData’ is not found anymore. It looks like he does not find the ancestor of our “OffServerService” service which is a “DARemoteService” service.

The peculiar thing is that this all works fine on my development machines.
It even works between a client on my development machine and a RO server on a test machine.
But it does not work when both client and server is on the test machine.

A server built with the same source code and an older RO version works.

The newest RO version (1537) still fails. Both in XE10.4 and XE11.x

Our code to find which service hosts a function is:
lService := GetRODLLibraryThreadSafe(Service).FindService(Service.ServiceName); //RODLService;
repeat
if lService=nil then
ls().LogInfo(‘Service "’+Service.ServiceName+’" not found’)
else
ls().LogInfo(‘Service "’+lService.Name+’" found’);
lOperation := lService.Default.FindOperation(aRemoteRequest.MethodName);
if not Assigned(lOperation) then
begin
if lService.Ancestor <> ‘’ then
lService := GetRODLLibraryThreadSafe(Service).FindService(lService.Ancestor)
//if Assigned (lService) then
// lOperation := lService.Default.FindOperation(aRemoteRequest.MethodName);
else
lService := nil;
end;
until Assigned(lOperation) or (not Assigned(lService));

FYI: To support DA3 we have to make 2 changes RO

  1. Copy dataabstract3.rodl from the legacy directory
  2. Change remobjects.inc by defining {$DEFINE TROPROXY_IGNORES_REMOTESERVICE_SERVICENAME}

Has something been broken?

Hi,

what build was installed before migrating to .1521?
last breaking change was in .1491

can you share server rodl, pls?
you can drop email to support@ for keeping privacy

can you create a simple testcase that reproduces this issues, pls?

We come from 10.0.0.1495
I will mail the RODL (it is a rodl based delphi server)
It would take me a lot of time to write a testcase.
Do you need both server and client code?

Are there any known dependencies? XMl parser? I am baffled that it works on some machines.

Hi,

I understood your issue - it can’t locate DataAbstract3.RODL on test machine because it hasn’t DAD installed.

what I can suggest:

  • consider to manually “detect” known members of IDARemoteService when DARemoteService ancestor is detected.
  • ship DataAbstract3.RODL & DataAbstract4.RODL with client and load it dynamically when needed. see examples how to load them manually at TXMLToRODL.LoadUses
  • switch some services to Code-First model. in this case all ancestor methods can be included into OffServerService service. see more about defines at https://talk.remobjects.com/t/remoting-sdk-for-delphi-vnext-new-features/18354/32

Regarding 1: From logs I deduce that the ancestor field is nil, so detecting is not possible (or teach me how)

Is there a way to simulate the issue on a development machine? That way I can debug and try to bypass the issue

Options 2 and 3 are laborous or not possible.

By the way: You propose we bypass the issue, does this mean the issue will not be fixed in RO?

Hi,

ServiceBuilder uses std ROD methods/classes and can read ancestor:

just change location of DataAbstract3.RODL in your RODL to unknown location, like c:\DataAbstract3.RODL

as for me, you “imagine” issue because RODL is needed only for generation of _Intf so you already have

  IDARemoteService = interface(IROService)
  ['{C532E842-0AA9-4253-A9BF-AFCF22885B97}']
    function GetDatasetSchema(const aDatasetName: AnsiString): Binary;
    function GetDatasetScripts(const DatasetNames: AnsiString): AnsiString;
    function GetDatasetData(const DatasetName: AnsiString; const Params: AnsiString; const IncludeSchema: Boolean; const MaxRecords: Integer): Binary;
    function GetDatasetDataEx(const DatasetName: AnsiString; const Params: TDADatasetParamArray; const UserFilter: AnsiString; const IncludeSchema: Boolean; const MaxRecords: Integer): Binary;
    function UpdateData(const Delta: Binary): Binary;
    function ExecuteSQLCommand(const SQL: AnsiString): Integer;
    function GetSchemaAsXML: AnsiString;
    function GetMultipleDatasets(const DatasetRequestInfoArray: TDADatasetRequestInfoArray): Binary;
    function ExecuteSQLCommandEx(const CommandName: AnsiString; const Params: TDADatasetParamArray): Integer;
  end;

IOffServerService = interface(IDARemoteService)
...

Regarding 1:
You show me how I can detect it manually.
I was asking how I can detect it programmatically

Is there a way to rewrite our client code (shown below, I realise I can hardcode all my services):

lService := GetRODLLibraryThreadSafe(Service).FindService(Service.ServiceName); //RODLService;
repeat
if lService=nil then
ls().LogInfo(‘Service "’+Service.ServiceName+’" not found’)
else
ls().LogInfo(‘Service "’+lService.Name+’" found’);
lOperation := lService.Default.FindOperation(aRemoteRequest.MethodName);
if not Assigned(lOperation) then
begin
if lService.Ancestor <> ‘’ then
lService := GetRODLLibraryThreadSafe(Service).FindService(lService.Ancestor)
//if Assigned (lService) then
// lOperation := lService.Default.FindOperation(aRemoteRequest.MethodName);
else
lService := nil;
end;
until Assigned(lOperation) or (not Assigned(lService));

Regarding bug or no bug:
Can you confirm this worked in RO 10.0.0.1495 and not in build 1521?
Or did we do something wrong/different?
Is there something wrong with our client ancestor loop?
As I see it the API provides an ancestor property which is not correct (to be verified once i can debug)

Hi,

something like

if lService.Ancestor <> '' then begin
  if lService.Ancestor = 'DARemoteService' then begin
    // do something
  end
  ...
end

I don’t see changes in generation of RODL for RODL-based services.
try to add {$UNDEF RO_RTTI_Support} and
{$UNDEF CodeFirst_Use_RODL_Uses} to RemObjects_user.inc. it may change something.

by some reasons, I can’t get generated RODL similar to your exportedrodl.rodl if I use OffServerLibrary.rodl as a main RODL for project…

btw, for security reasons, some companies disable server.ServeRODL , server.ServeInfoPage, etc properties in release mode because this can be considered as a security hole.

Hi,

  1. Our previous release to our customers was 1489, I am not sure if 1495 does or does not have this issue.

  2. I have debugged te code and indeed the Ancestor property of our service is still correct. It is the TRORemoteService.GetRODLLibrary which does not load/return a service.
    So I could bypass the issue for some known operations as you suggest.

  3. Have you also moved the da3 rodl file? Maybe that’s why you cannot reproduce? Note: My tests now are with v1521

  4. This is an internal RO service (LAN, not internet) I assume we needn’t worry about the security hole. However we have some HTTPS based internet RO services and I have been wondering about the security for these. I’ll leave that for another support issue.

Hi,

the main difference - services from da3/da4 were included into generated RODL:

so you should be able to detect ancestor services …

I’ve updated location to legacy folder so DA3.RODL is loaded correctly …

We are using this code that searches for the RODL services to update the remote data adapter parameters in case different functions are used. Below you find the source code of the entire function.
I guess that means that your solution of detecting the ‘DARemoteService’ ancestor cannot be used?
We need to set the parameter list. How can we do this if we cannot load the RODL library?

procedure TOffBaseDATable.XLSRefreshParams (aRemoteRequest: TDARemoteRequest);
var
  lOperation: TRODLOperation;
  lService: TRODLService;
  newparam:TDARemoteRequestParam;
  //lParam: TDARemoteRequestParam;
  lOldParams: TDARemoteRequestParams;
  //lOperationParam: TRODLOperationParam;
  i: Integer;
begin
   if not Assigned (aRemoteRequest) then
      Raise Exception.Create (SNoRemoteRequestParameterAssigned);
//   if not Assigned (RemoteService) then
//      Raise Exception.Create(SNoRemoteServiceAssigned);
//   if RemoteService.ServiceName = '' then
//      Raise Exception.Create (SServiceNamePropertyOf + RemoteService.Name + SIsEmpty);
//   if RODLLibrary = nil then
//      Raise Exception.Create (SRODLLibraryNotAssigned);
//   if RODLService = nil then
//      Raise Exception.Create (SRODLServiceNotAssigned);

   (* KCV --- 3/08/2007 13:45:49 *)
   // Loosely coupling
   lService := GetRODLLibraryThreadSafe(Service).FindService(Service.ServiceName); //RODLService;
   repeat
     if lService=nil then
       ls().LogInfo('Service "'+Service.ServiceName+'" not found')
     else
       ls().LogInfo('Service "'+lService.Name+'" found');
     lOperation := lService.Default.FindOperation(aRemoteRequest.MethodName);
     if not Assigned(lOperation) then
     begin
       if lService.Ancestor <> '' then
       begin
         if CompareText(lService.Ancestor,'DARemoteService')=0 then
         begin
           if aRemoteRequest.MethodName='UpdateData' then
             Exit;
         end;
         lService := GetRODLLibraryThreadSafe(Service).FindService(lService.Ancestor)
       end
       else
       begin
         lService := nil;
       end;
     end;
   until Assigned(lOperation) or (not Assigned(lService));

   if not Assigned(lOperation) then
      Raise Exception.Create(SMethod + aRemoteRequest.MethodName + SNotFoundInService);
                            // +
                            // RemoteService.ServiceName + SOrItsAnchestors);

    lOldParams := TDARemoteRequestParams.Create(nil);
    for i := 0 to (aRemoteRequest.Params.Count - 1) do
    begin
      with lOldParams.Add do
      begin
        Name := aRemoteRequest.Params[i].Name;
        Value := aRemoteRequest.Params[i].Value;
      end;
    end;

    aRemoteRequest.Params.Clear;

    if (lOperation.Result<>NIL) then
    begin
      newparam := aRemoteRequest.Params.Add();
      newparam.CopyRODLParam(lOperation.Result, TRUE, lOldParams);
    end;

    for i := 0 to (lOperation.Count - 1) do
    begin
      newparam := aRemoteRequest.Params.Add();
      newparam.CopyRODLParam(lOperation.Items[i], TRUE, lOldParams);
    end;

    FreeAndNil(lOldParams);

//    for i := 0 to (lOperation.Count - 1) do
//    begin
//       lParam := aRemoteRequest.Params.Add();
//       lOperationParam := lOperation.Items[i];
//       lParam.Name := lOperationParam.Name;
//       lParam.ParamType := lOperationParam.Flag;
//       lParam.DataType := StrToDataType(lOperationParam.DataType);
//    end;
end;
  1. Is this an intentional difference? Is this the reason why our code broke? If it is intentional/permanent, how should we dynamically update our remote data adapter parameters now based on custom specified operations? A solution where RODL files need to be shipped is not elegant, the cleanest solution IMO is where the server gives information about all of it’s operations (even the ones in the ancestors)

  2. After installing RO we always copy DataAbstract3.RODL from source/legacy to source. At development time this sufficed (and at runtime/deployed everything worked also). Are you now saying this copy operation will not be needed in future versions of RO?

Hi,

check uDARemoteDataAdapterRequests.pas unit.
each class (like TDAUpdateDataRequest) has 2 methods:

  • SetupDefaultRequestV3
  • SetupDefaultRequest

so you can initialize request w/o reading RODL just knowing ancestor (DA3 or DA4 DataService).

Note: RDA also has these methods (SetupDefaultRequestV3 and SetupDefaultRequest)

try to create a new project and add DA3 uses.
it will be referenced to $(Data Abstract for Delphi)\Source\Legacy\DataAbstract3.RODL.
so you shouldn’t copy DA3 RODL to other place

Do you have samples of how SetupDefaultRequestV3 can be used?
My attempt (see below) fails.

I will furthermore be needing a way to copy the values
How do I achieve what CopyRODLParam(lOperation.Result, TRUE, lOldParams); does?
I am guessing the old params are copied?
The documentation does not make myself wiser (TRORequestParam)

   if lService.Ancestor <> '' then
   begin
     if CompareText(lService.Ancestor,'DARemoteService')=0 then
     begin
       if CompareText(aRemoteRequest.MethodName,'UpdateData')=0 then
       begin
         Assert(aRemoteRequest is TDAUpdateDataRequest);
         if aRemoteRequest is TDAUpdateDataRequest then
           TDAUpdateDataRequest(aRemoteRequest).SetupDefaultRequestV3();
         Exit;
       end;
       if CompareText(aRemoteRequest.MethodName,'GetDatasetSchema')=0 then
       begin
         Assert(aRemoteRequest is TDAGetSchemaRequest);
         if aRemoteRequest is TDAGetSchemaRequest then
           TDAGetSchemaRequest(aRemoteRequest).SetupDefaultRequestV3();
         Exit;
       end;
     end;
     lService := GetRODLLibraryThreadSafe(Service).FindService(lService.Ancestor)
   end

FYI: The code I posted does not work, call to
GetSchemaCall.ParamByName(‘aDataSetName’).Value := GetTableName;
throws an exception (the parameter is not found) but looking at uDARemoteDataAdapterRequest I indeed do not see ‘aDataSetName’ in TDAGetSchemaRequest

Hi,

why you can’t just do something like

?

This is more or less what I do.
I am passing RemoteDataAdapter.GetSchemaCall to the function I posted (I have to cast the TRODynamicRequest to the actual type)

I think however the issue is the descrepancy betweenthe parameters in the IDARemoteService you posted above and the parameters in uDARemoteDataAdapterRequest (I do not see aDataSetName there)

Meanwhile I have specified
{$UNDEF RO_RTTI_Support}
{$UNDEF CodeFirst_Use_RODL_Uses}
in RemObjects_user.inc
but this does not change the behaviour. Should I do something after modifying this file?
I only did a full build of server and client.

Hi,

Can you create a very simple testcase that reproduces this, pls? it can load RODL from file for simplification.

it was a chance that it could change something, but it doesn’t …
you can remove these changes from RemObjects_user.inc.