Project

General

Profile

Calling 4GL Code via the Appserver API

Introduction

FWD provides support for the conversion and execution of 4GL code via appserver. This can be used for integrating with external systems, but some considerations/effort is needed.

Replacing 4GL Access

TBD: This can only be done from converted code and it is transparent when inside a single server. We need to detail this and also explain how cross-server access can be made to work.

Replacing the Open Client for Java

Introduction

Remote access to 4GL code from Java is supported using a replacement for the Open Client for Java (an OpenEdge facility). This replacement is a set of classes in FWD. These classes are not binary-compatible with the OpenEdge code (Open Client for Java). Instead, the equivalent features exist and existing code must be reworked to use different classes.

Guide

The FWD OpenClient replacement relies on the proxy programs used by the legacy OpenClient to be also converted as 'Java proxy files'. This is done by using as input the same .xpxg files which are required by ProxyGen to generate the .java code; for configuring this in FWD, see ProxyGen and OpenClient Usage.

In FWD, the conversion follows a similar (but not identical) code generation output as ProxyGen; this includes:
  • for each AppObject or SubAppObject, a different .java class is created; all these classes will extent LegacyJavaAppserverClientProxy, and each class will contain:
    • APIs to execute non-persistent external programs
    • APIs to create an instance for the proxy of a persistent/singleton/single-run program (note: this does not execute the call)
  • for each proxied 4GL program which is ran persistent, singleton or single-run, a different class is created, extending LegacyJavaAppserverClientProxy. This will contain Java methods to execute each internal procedure or function being proxied, plus helper APIs to create table or dataset metadata definitions.
This guide will show how to migrate the legacy proxies for these programs:
  • a testargs.p program, ran persistent, with these internal entries:
    procedure test_input.
       def input param p1 as char.
       def input param p2 as date.
       def input param p3 as datetime.
       def input param p4 as datetime-tz.
       def input param p5 as dec.
       def input param p6 as int.
       def input param p7 as int64.
       def input param p8 as log.
       def input param p9 as longchar.
       def input param p10 as memptr.
       def input param p11 as raw.
       def input param p12 as recid.
       def input param p13 as rowid.
    
       def output param res as char.
    
    end.
    
    function test_func_input returns char(
       input p1 as char,
       input p2 as date,
       input p3 as datetime,
       input p4 as datetime-tz,
       input p5 as dec,
       input p6 as int,
       input p7 as int64,
       input p8 as log,
       input p9 as longchar,
       input p10 as memptr,
       input p11 as raw,
       input p12 as recid,
       input p13 as rowid):
    end.
    
  • a testtable.p program, ran persistent, with this definition:
    def temp-table tt1 no-undo
    field f1 as blob
    field f2 as clob
    field f3 as char
    field f4 as date
    field f5 as datetime
    field f6 as datetime-tz
    field f7 as decimal
    field f8 as int
    field f9 as int64
    field f10 as log initial ?
    field f11 as raw
    field f12 as recid
    field f13 as rowid
    field f14 as int extent 3.
    
    procedure test_table_fields.
       def input-output parameter table for tt1.
    
  • a testdataset.p program, ran persistent, with this definition:
    def temp-table tt2 field g1 as int.
    def temp-table tt3 field g2 as int.
    def dataset ds2 for tt2, tt3 data-relation dr1 for tt2, tt3 relation-fields(g1,g2) nested.
    
    procedure test_dataset.
       def input-output parameter dataset for ds2.
    
       find first tt2.
       tt2.g1 = tt2.g1 * 100.
    
       find first tt3.
       tt3.g2 = tt3.g2 * 100.
    
       return "test_dataset".
    end.
    
  • a testextprog.p program, non-persistent, with this definition:
    def input param p1 as char.
    def output param p2 as char.
    
  • a subtype/testpersistent.p program, with this definition:
    def input param i as int.
    
    procedure test1.
       def input param p1 as char.
       def output param p2 as char.
       def output param p3 as datetime-tz.
       i = i + 1.
    
       p2 = "Hello " + p1 + ", counter is " + string(i).
       p3 = now.
    end.
    
ProxyGen was used to generate a .xpxg file with this configuration:
  • the root AppObject named open_client_sample , with:
    • non-persistent: testextprog.p
    • persistent: testargs.p, testtable.p and testdataset.p
  • a SubAppObject named subtype, with:
    • testpersistent.p ran persistent
In OpenEdge, these .java files would be generated (excluding their Impl):
  • open_client_sample.java for the root AppObject, with:
    • createAO_subtype to create an instance for the subtype SubAppObject
    • createPO_testargs() for the testargs.p persistent proxy
    • createPO_testtable() for the testtable.p persistent proxy
    • createPO_testdataset() for the testdataset.p persistent proxy
    • String testextprog(String p1, StringHolder p2) for the testextprog non-persistent proxy
  • testargs.java, for the testargs.p program, with:
    • String test_func_input(...) method, for the internal function
    • String test_input(...) method, for the internal procedure
  • testpersistent.java, for the subtype/testpersistent.p program, with:
    • String test1(String p1, StringHolder p2, DateHolder p3), for the internal procedure
  • testtable.java, for the testtable.p program, with:
    • public String test_table_fields(ProDataGraphHolder tt1), for the internal procedure
  • testdataset.java, for the testdataset.p program, with:
    • public String test_dataset(ProDataGraphHolder ds2), for the internal procedure
  • subtype.java, with:
    • testpersistent createPO_testpersistent(Integer i), for subtype/testpersistent.p
In FWD, these .java files will be generated by conversion:
  • each .java file will have a constructor accepting a single LegacyJavaAppserverClient client argument, to create a new instance using the specified FWD client connection
  • open_client_sample.java, for the root AppObject, with:
    • public testargs new_testargs(), to create a helper instance to execute testargs.p
    • LegacyJavaAppserverProxyInvocationResult testextprog(String p1, String p2), to execute the testextprog.p program
    • testtable new_testtable(), to create a helper instance to execute testtable.p
    • testdataset new_testdataset(), to create a helper instance to execute testdataset.p
  • testargs.java, for the testargs.p program, with:
    • LegacyJavaAppserverProxyInvocationResult test_input(...), for the internal procedure
    • LegacyJavaAppserverProxyInvocationResult test_func_input(...), for the internal function
  • testpersistent.java, for the subtype/testpersistent.p program, with:
    • LegacyJavaAppserverProxyInvocationResult runPersistent(Integer i), to run this program persistent
    • LegacyJavaAppserverProxyInvocationResult test1(String p1, String p2, java.util.GregorianCalendar p3), for the internal procedure
  • testtable.java, for the testtable.p program, with:
    • public LegacyJavaAppserverProxyInvocationResult runPersistent(), to run the program persistent
    • public LegacyJavaAppserverProxyInvocationResult test_table_fields(DataGraph tt1), to execute the internal procedure
    • public static DataGraph create_tt1(), to create a data-graph for the tt1 temp-table.
  • testdataset.java, for the testdataset.p program, with:
    • public LegacyJavaAppserverProxyInvocationResult runPersistent(), to run the program persistent
    • public LegacyJavaAppserverProxyInvocationResult test_dataset(DataGraph ds2), to execute the internal procedure
    • public static DataGraph create_tt3(), to create a data-graph for the tt3 temp-table.
    • public static DataGraph create_tt2(), to create a data-graph for the tt2 temp-table.
    • public static DataGraph create_ds2(), to create a data-graph for the ds2 dataset.
  • subtype.java, for the subtype SubAppObject, with:
    • public testpersistent new_testpersistent(), to create a helper instance to execute subtype/testpersistent.p

Executing a Proxy Request

A request in the legacy OpenClient is performed via:
  • actually calling the Java method associated with the required external program (passing all required arguments), from the AppObject or SubAppObject instance, which will:
    • return a String value, for the RETURN-VALUE from the external program
    • return the instance associated for that proxy program, which will allow you to execute the internal function/procedures.

In the following examples, sample is an instance of open_client_sample:

Example 1: execute external program testextprog.p:

      StringHolder p2 = new StringHolder();
      String ret = sample.testextprog("Welcome!", p2);

Example 2: execute persistent program testargs.p and an internal function in it:

testargs test = sample.createPO_testargs();
String ret = test.test_func_input(...);
test._release();

Example 3: obtaining a SubAppObject instance and executing a persistent program:

subtype sub = sample.createAO_subtype();
testpersistent test1 = sub.createPO_testpersistent(0);
test1._release();
sub._release();

In FWD, the requests can be executed in a similar manner:
  • a connection LegacyJavaAppserverClient fwd connection is assumed to be available
  • the AppObject instance is created via open_client_sample sample = new open_client_sample(fwd);
  • the Java method associated with the required external program or internal function/procedure is executed
    • in case of programs ran persistent, after obtaining the proxy instance, the runPersistent(...) method is used to execute the target

Example 1: execute external program testextprog.p:

         String p1 = "Welcome!";
         String p2 = null;
         LegacyJavaAppserverProxyInvocationResult res = sample.testextprog(p1, p2);

Example 2: execute persistent program testargs.p and an internal function in it:

      testargs testargs = new testargs(fwd);
      testargs.runPersistent();
      LegacyJavaAppserverProxyInvocationResult ret1 = testargs.test_func_input(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13);
      testargs.dispose();

Example 3: obtaining a SubAppObject instance and executing a persistent program:

      subtype sub = new subtype(fwd);
      testpersistent test1 = sub.new_testpersistent();
      test1.runPersistent(0);

Data Types

FWD and OpenEdge both use Java-types for most of the possible types, with some exceptions; bellow is the mapping of 4GL types to OpenEdge OpenClient and FWD OpenClient:
Type OpenEdge FWD
char String String
date GregorianCalendar GregorianCalendar
datetime GregorianCalendar GregorianCalendar
datetime-tz GregorianCalendar GregorianCalendar
decimal BigDecimal BigDecimal
integer int int
int64 long long
logical boolean boolean
longchar String String
memptr Memptr MemoryBuffer
raw byte[] byte[]
recid long long
rowid Rowid long

If proxies are generated with the Enable Unknowns for Parameters and Return Values flag, then parameters will be converted using their equivalent boxed types in Java, instead of Java native types - this is done in both FWD and OpenEdge.

Passing Arguments and Return Value

All input modes are supported in FWD - INPUT, OUTPUT and INPUT-OUTPUT. The argument's type needs to be one from the table in previous section.

Each argument needs to be specified with the correct the FWD Java type, regardless of mode. The distinction between FWD and OpenEdge are the OUTPUT or INPUT-OUTPUT arguments: in OE, specific *Holder classes are used to transfer these arguments. In FWD, the argument is passed, and
  • for an OUTPUT, its value is ignored and not transferred to the remote side
  • for INPUT-OUTPUT, its value is transferred to the remote side
After the request returns to the caller, the OUTPUT or INPUT-OUTPUT arguments can be read using APIs in the LegacyJavaAppserverProxyInvocationResult instance, which is returned in FWD by all OpenClient calls; these APIs all receive an 1-based argument index, to read the returned value for the argument
  • Object getOutputParameter - get an 'un-casted' reference to that argument
  • Integer getIntOutputParameter
  • Integer[] getIntArrayOutputParameter
  • String getStringOutputParameter
  • String[] getStringArrayOutputParameter
  • Double getDoubleOutputParameter
  • Double[] getDoubleArrayOutputParameter
  • BigDecimal getBigDecimalOutputParameter
  • BigDecimal[] getBigDecimalArrayOutputParameter
  • Boolean getBooleanOutputParameter
  • Boolean[] getBooleanArrayOutputParameter
  • Long getLongOutputParameter
  • Long[] getLongArrayOutputParameter
  • GregorianCalendar getCalendarOutputParameter
  • GregorianCalendar[] getCalendarArrayOutputParameter
  • byte[] getByteArrayOutputParameter
  • byte[][] getByteArrayArrayOutputParameter
  • MemoryBuffer getMemoryBufferOutputParameter
  • MemoryBuffer[] getMemoryBufferArrayOutputParameter
  • DataGraph getDataSetOutputParameter
  • DataGraph getTableOutputParameter

Unknown values will be represented by a null.

To get the value from a function call, Object getFunctionReturn() can be used (WARNING - extent functions are not supported at this time).

To get the RETURN-VALUE from a procedure or external program call, use String getReturnValue()

OpenEdge Example with arguments:

      subtype sub = sample.createAO_subtype();

      testpersistent test1 = sub.createPO_testpersistent(0);
      DateHolder time = new DateHolder();
      StringHolder res = new StringHolder();
      test1.test1("Testing:" , res, time);
      System.out.println("testpersistent.test1: " + res.getStringValue() + " " + Utils.formatDatetimetz(time.getDateValue()));

      sub._release();
      test1._release();        

FWD Example with arguments:

      subtype sub = new subtype(fwd);
      GregorianCalendar time = null;
      String res = null;
      LegacyJavaAppserverProxyInvocationResult ret = null;

       String p1 = "Testing:";
       testpersistent test1 = sub.new_testpersistent();
       test1.runPersistent(0);

       ret = test1.test1(p1, res, time);
       res = ret.getStringOutputParameter(2);
       time = ret.getCalendarOutputParameter(3);
       System.out.println("testpersistent.test1: " + res + " " + Utils.formatDatetimetz(time));

       test1.dispose();
       sub.dispose();

Managing Exceptions

Exceptions thrown from a remote call in FWD will be either:
  • a com.goldencode.p2j.util.ConditionException, in case of conditions throw by the remote side (ERROR, QUIT, STOP, or structured OO exceptions).
  • wrapped in a com.goldencode.p2j.util.RuntimeAppserverError, otherwise

Both types are unchecked exceptions; explicit management by the application code needs to catch and treat them, as needed.

Working with TEMP-TABLE and DATSET parameters

FWD OpenClient supports both temp-table and dataset parameters for the remote call. Underlying, the same core SDO implementation is used (see Tuscany SDO).

The temp-table and dataset definitions are included in the generated Java proxy files - it will include all table fields, table indexes, dataset definitions and relations, and the before-image support.

The metadata for a temp-table in i.e. testtable.p, will be emitted in the proxy Java file, and in FWD will look like:

   public static final String tt1 = "tt1";

   public static DataGraph create_tt1()
   {
      DataObject table = HELPER.createObjectMetaData(tt1, 14, false, 0, "", null, null);
      HELPER.setFieldMetaData(table, 1, "f1", 0, HELPER.blob(), 0, 0);
      HELPER.setFieldMetaData(table, 2, "f2", 0, HELPER.clob(), 10, 0);
      HELPER.setFieldMetaData(table, 3, "f3", 0, HELPER.character(), 20, 0);
      HELPER.setFieldMetaData(table, 4, "f4", 0, HELPER.date(), 30, 0);
      HELPER.setFieldMetaData(table, 5, "f5", 0, HELPER.datetime(), 40, 0);
      HELPER.setFieldMetaData(table, 6, "f6", 0, HELPER.datetimetz(), 50, 0);
      HELPER.setFieldMetaData(table, 7, "f7", 0, HELPER.decimal(), 60, 0);
      HELPER.setFieldMetaData(table, 8, "f8", 0, HELPER.integer(), 70, 0);
      HELPER.setFieldMetaData(table, 9, "f9", 0, HELPER.int64(), 80, 0);
      HELPER.setFieldMetaData(table, 10, "f10", 0, HELPER.logical(), 90, 0);
      HELPER.setFieldMetaData(table, 11, "f11", 0, HELPER.raw(), 100, 0);
      HELPER.setFieldMetaData(table, 12, "f12", 0, HELPER.recid(), 110, 0);
      HELPER.setFieldMetaData(table, 13, "f13", 0, HELPER.rowid(), 120, 0);
      HELPER.setFieldMetaData(table, 14, "f14", 3, HELPER.integer(), 130, 0);
      DataObject metadata = HELPER.createGraphMetaData(tt1);
      HELPER.addTableMetaData(metadata, table);
      return HELPER.createDataGraph(metadata);
   }

while in OE would look like:

    static ProDataGraphMetaData test_table_fields_DSMetaData1;

    static ProDataObjectMetaData test_table_fields_MetaData11;

    static
    {
        test_table_fields_DSMetaData1 = new ProDataGraphMetaData(0, "tt1", 1, ParameterSet.INPUT_OUTPUT);
        test_table_fields_MetaData11 = new ProDataObjectMetaData("tt1", 14, false, 0, null, null, null);
        test_table_fields_MetaData11.setFieldDesc(1, "f1", 0, Parameter.PRO_BLOB,0,0);
        test_table_fields_MetaData11.setFieldDesc(2, "f2", 0, Parameter.PRO_CLOB,1,0);
        test_table_fields_MetaData11.setFieldDesc(3, "f3", 0, Parameter.PRO_CHARACTER,2,0);
        test_table_fields_MetaData11.setFieldDesc(4, "f4", 0, Parameter.PRO_DATE,3,0);
        test_table_fields_MetaData11.setFieldDesc(5, "f5", 0, Parameter.PRO_DATETIME,4,0);
        test_table_fields_MetaData11.setFieldDesc(6, "f6", 0, Parameter.PRO_DATETIMETZ,5,0);
        test_table_fields_MetaData11.setFieldDesc(7, "f7", 0, Parameter.PRO_DECIMAL,6,0);
        test_table_fields_MetaData11.setFieldDesc(8, "f8", 0, Parameter.PRO_INTEGER,7,0);
        test_table_fields_MetaData11.setFieldDesc(9, "f9", 0, Parameter.PRO_INT64,8,0);
        test_table_fields_MetaData11.setFieldDesc(10, "f10", 0, Parameter.PRO_LOGICAL,9,0);
        test_table_fields_MetaData11.setFieldDesc(11, "f11", 0, Parameter.PRO_RAW,10,0);
        test_table_fields_MetaData11.setFieldDesc(12, "f12", 0, Parameter.PRO_RECID,11,0);
        test_table_fields_MetaData11.setFieldDesc(13, "f13", 0, Parameter.PRO_ROWID,12,0);
        test_table_fields_MetaData11.setFieldDesc(14, "f14", 3, Parameter.PRO_INTEGER,13,0);
        test_table_fields_DSMetaData1.addTable(test_table_fields_MetaData11);

    }

(note how the fields are package-private and can't be easily accessed by other application code).

The metadata is just the definition of the table, but in FWD, i.e. create_tt1 for a temp-table will create an equivalent DataGraph, which can then be used to populate with rows and transfer to the remote side, at the proxy call.

Create a temp-table instance in FWD is as easy as calling create_tt1 on the proxy instance:

      testtable tf = new testtable(fwd);
      DataGraph tt1 = tf.create_tt1();

The DataGraph is the container which holds the temp-table or dataset:
  • in case of temp-tables, it will hold a single table with the known name
  • in case of datasets, it will hold all tables and relations between them

For datasets, FWD emits APIs in the Java code for the proxy program, to create the metadata for the dataset and all associated temp-tables, like this in the i.e. testdataset.p associated proxy:

   public static final String tt3 = "tt3";

   public static final String tt2 = "tt2";

   public static final String ds2 = "ds2";

   public static DataGraph create_tt3()
   {
      DataObject table = HELPER.createObjectMetaData(tt3, 1, false, 0, "", null, null);
      HELPER.setFieldMetaData(table, 1, "g2", 0, HELPER.integer(), 0, 0);
      DataObject metadata = HELPER.createGraphMetaData(tt3);
      HELPER.addTableMetaData(metadata, table);
      return HELPER.createDataGraph(metadata);
   }

   public static DataGraph create_tt2()
   {
      DataObject table = HELPER.createObjectMetaData(tt2, 1, false, 0, "", null, null);
      HELPER.setFieldMetaData(table, 1, "g1", 0, HELPER.integer(), 0, 0);
      DataObject metadata = HELPER.createGraphMetaData(tt2);
      HELPER.addTableMetaData(metadata, table);
      return HELPER.createDataGraph(metadata);
   }

   public static DataGraph create_ds2()
   {
      DataObject metadata = HELPER.createGraphMetaData(ds2);
      HELPER.addTableMetaData(metadata, create_tt2());
      HELPER.addTableMetaData(metadata, create_tt3());
      HELPER.addRelation(metadata, "dr1", tt2, tt3, null, true, false, "g1,g2", false, false, true, false);
      return HELPER.createDataGraph(metadata);
   }

while in OE it would look like:
    static ProDataGraphMetaData test_dataset_DSMetaData1;

    static ProDataObjectMetaData test_dataset_MetaData11;

    static ProDataObjectMetaData test_dataset_MetaData12;

    static ProDataRelationMetaData test_dataset_MetaLink11;

    static
    {
        test_dataset_DSMetaData1 = new ProDataGraphMetaData(0, "ds2", 1, ParameterSet.INPUT_OUTPUT);
        test_dataset_MetaData11 = new ProDataObjectMetaData("tt2", 1, false, 0, null, null, null);
        test_dataset_MetaData11.setFieldDesc(1, "g1", 0, Parameter.PRO_INTEGER,0,0);
        test_dataset_DSMetaData1.addTable(test_dataset_MetaData11);
        test_dataset_MetaData12 = new ProDataObjectMetaData("tt3", 1, false, 0, null, null, null);
        test_dataset_MetaData12.setFieldDesc(1, "g2", 0, Parameter.PRO_INTEGER,0,0);
        test_dataset_DSMetaData1.addTable(test_dataset_MetaData12);
        test_dataset_MetaLink11 = new ProDataRelationMetaData("dr1", 0, 1, 1, "g1,g2", 2, null);
        test_dataset_DSMetaData1.addDataRelation(test_dataset_MetaLink11);

    }

LegacyJavaAppserverClientProxy (a super-class of all proxy types in FWD OpenClient) has helper APIs to:
  • create a row, DataObject createRow(DataGraph graph, String tableName)
  • add the row to the table, addRow(DataGraph, DataObject)
  • get the rows in a table, List<DataObject> listRows(DataGraph graph, String table)

As an example, creating a row in FWD OpenClient looks like this:

      DataObject tt1Row1 = tf.createRow(tt1, "tt1");
      tf.addRow(tt1, tt1Row1);

Once the row is created, the DataObject instance will be used to set any fields:

      tt1Row1.set("f1", "123456789".getBytes());
      tt1Row1.set("f2", "asdfghj");
      tt1Row1.set("f3", "abc");
      tt1Row1.set("f4", com.goldencode.Utils.fromIso8601("2022-04-15"));
      tt1Row1.set("f5", com.goldencode.Utils.fromIso8601("2022-04-15T07:03:01.000"));
      tt1Row1.set("f6", com.goldencode.Utils.fromIso8601("2022-04-15T07:03:01.000+02:00"));
      tt1Row1.set("f7", new BigDecimal("1234.56"));
      tt1Row1.set("f8", 12345);
      tt1Row1.set("f9", 123456789l);
      tt1Row1.set("f10", true);
      tt1Row1.set("f11", "abcdefg".getBytes());
      tt1Row1.set("f12", 123456789l);
      tt1Row1.set("f13", "12345");
      tt1Row1.set("f14", Arrays.asList(111, 222, 333));

and perform the call:
LegacyJavaAppserverProxyInvocationResult res = tf.test_table_fields(tt1);

Otherwise, com.goldencode.p2j.util.LegacyJavaAppserverApi interface (implemented by the LegacyJavaAppserverClient type), has other helpers to work with the metadata of a table or dataset, on a more low-level approach, including but not limited to reading the fields from a table metadata, working with the indexes, creating a dataset or table DataGraph from scratch, etc.

In the legacy OpenClient, working with the dataset or table parameters is similar:
  • create the DataGraph for a temp-table
             ProDataGraphMetaData tt1Param = new ProDataGraphMetaData(0, "tt1", 1, ParameterSet.INPUT_OUTPUT);
             tt1Param.addTable(tt1TableMetaData);
             ProDataGraph tt1 = new ProDataGraph(tt1Param);
    
  • create the DataGraph for a dataset:
             ProDataGraphMetaData ds2MetaData = new ProDataGraphMetaData(0, "ds2", 1, ParameterSet.INPUT_OUTPUT);
    
             ProDataObjectMetaData tt2MetaData = new ProDataObjectMetaData("tt2", 1, false, 0, null, null, null);
             tt2MetaData.setFieldDesc(1, "g1", 0, Parameter.PRO_INTEGER,0,0);
             ds2MetaData.addTable(tt2MetaData);
    
             ProDataObjectMetaData tt3MetaData = new ProDataObjectMetaData("tt3", 1, false, 0, null, null, null);
             tt3MetaData.setFieldDesc(1, "g2", 0, Parameter.PRO_INTEGER,0,0);
             ds2MetaData.addTable(tt3MetaData);
             ProDataRelationMetaData dsRel = new ProDataRelationMetaData("dr1", 0, 1, 1, "g1,g2", 2, null);
             ds2MetaData.addDataRelation(dsRel);
    
             ProDataGraph ds2 = new ProDataGraph(ds2MetaData);
    
  • create and add a row in a table parameter:
             ProDataObject tt1Row1 = tt1.createProDataObject("tt1");
             tt1.addProDataObject(tt1Row1);
    
  • create and add rows to dataset tables:
             ProDataObject tt2Row = ds2.createProDataObject("tt2");
             ProDataObject tt3Row = ds2.createProDataObject("tt3");
             ds2.addProDataObject(tt2Row);
             ds2.addProDataObject(tt3Row);
    
  • setting the fields is the same as in FWD
  • perform the call for a table parameter:
             ProDataGraphHolder tt1Ref = new ProDataGraphHolder();
             tt1Ref.setProDataGraphValue(tt1);
    
             String ret = test.test_table_fields(tt1Ref);
    
  • perform the call for a dataset parameter:
             ProDataGraphHolder ds2Ref = new ProDataGraphHolder(ds2);
             String res = test.test_dataset(ds2Ref);
    

TBD: We need to write a guide on how to do this including some example code.

TBD: Link to the API reference to the FWD classes that are the replacements (this probably can just link to our existing javadoc).

Configuration and Setup

Generic details can be found in Installation, Configuration and Administration Guide - here information can be found how to configure the FWD server in terms of creating accounts, establishing permissions, etc.

This section describes how to configure the FWD server to allow OpenClient connections from a remote application.

TBD: add details of this access from within the same JVM as the FWD server.

It is recommended that the connection to the FWD server is performed using security certificates. This requires that the FWD server has a process account configured to authenticate via a certificate - see Account setup.

The configured account will need to have its private-key stored in an external store file - a self-signed certificate (see Cryptography_Setup_Helper) is enough for this purposes, as the connection is assumed to be made from the same host or on a secure network, as the FWD server's ports will not be exposed over the internet.

The created account doesn't need any special permissions, other than connect to the FWD server.

Establishing a Connection

In OpenEdge, a connection is established by specifying the appserver URL to a com.progress.open4gl.javaproxy.Connection instance or to the actual AppObject instance. In FWD, the connection is established via a com.goldencode.p2j.util.LegacyJavaAppserverClient - this instance is obtained by connecting to the FWD server, via LegacyJavaAppserverClient.connectRemote(BootstrapConfig cfg, String appserver, boolean sessionFree).

BootstrapConfig cfg is used to configure the connection to the FWD server - the full details can be found at Configuration Reference. For the purposes of our connection from a remote app to the FWD server, the connection can be specified either secure or insecure.

For the secure sockets case, the required configuration looks like:
         BootstrapConfig cfg = new BootstrapConfig();
         cfg.setServer(false);
         cfg.setConfigItem("net", "connection", "secure", "true");
         cfg.setConfigItem("net", "server", "host", host);
         cfg.setConfigItem("net", "server", "secure_port", port);
         cfg.setConfigItem("security", "keystore", "processalias", alias);
         cfg.setConfigItem("security", "keystore", "filename", keystore);
         cfg.setConfigItem("access", "password", "keystore", keypass);
         cfg.setConfigItem("access", "password", "keyentry", entrypass);

         cfg.setConfigItem("security", "truststore", "filename", truststore);
         cfg.setConfigItem("security", "truststore", "alias", trustalias);

where:
  • net:connection:secure forces the connection to be made via SSL
  • net:server:host is the target FWD server's host
  • net:server:secure_port is the target FWD server's secure port
  • security:keystore:processalias is the alias of a process account configured on the FWD server
  • security:keystore:filename is the JKS store file with the private key and certificate for this account (under the processalias above)
  • access:password:keystore is the password for the store file
  • access:password:keyentry is the password for the private-key entry in the store file
  • security:truststore:filename is the store file which includes the FWD server's certificate and the root certificate (usually named srv-certs.store)
  • security:truststore:alias is the certificate alias for the FWD server, in the trust-store file

String appserver represents the appserver name to which the connection is made.

boolean sessionFree will be:
  • true if the target appserver is State-free
  • false otherwise
For insecure sockets case, the only difference is in the bootstrap configuration:
         BootstrapConfig cfg = new BootstrapConfig();
         cfg.setServer(false);
         cfg.setConfigItem("net", "connection", "secure", "false");
         cfg.setConfigItem("net", "server", "host", host);
         cfg.setConfigItem("net", "server", "insecure_port", port);

where:
  • net:connection:secure forces the connection to be made insecure
  • net:server:host is the target FWD server's host
  • net:server:insecure_port is the target FWD server's insecure port

Note that in this example, there is no account specified - instead, the FWD server is assumed to be configured for 'guest access' - see Authentication.

Important the instance returned by LegacyJavaAppserverClient.conenctRemote is not thread-safe and is not meant to be reused across threads - ensure that a connection is established on each i.e. web request, and disconnect() is called on it before the request finishes.

Replacing the Open Client for .NET

FWD does not have a replacement for the Open Client for .NET, but such a replacement is possible. See #3326 for the enabling work that is needed.

FWD allows to run the refactored app code (from .NET to Java) in a compatibility mode with the 4GL .NET OpenClient. This can be done by calling setDotNetCompatibility on the LegacyJavaAppserverClient instance obtained via connectRemote; this will:
  • force all extent fields to be denormalized, so a field foo[4] can be accessed via foo1, foo2, foo3, foo4.
  • tracking-changes is enabled by default
  • each BigDecimal will have a scale of at least 1
  • when datasets are used, if the caller sends as argument a built dataset, then the response will merged into this dataset, instead of creating a new dataset

© 2004-2026 Golden Code Development Corporation. ALL RIGHTS RESERVED.