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
AppObjectorSubAppObject, a different .java class is created; all these classes will extentLegacyJavaAppserverClientProxy, 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.
- a
testargs.pprogram, 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.pprogram, 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.pprogram, 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.pprogram, non-persistent, with this definition:def input param p1 as char. def output param p2 as char.
- a
subtype/testpersistent.pprogram, 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.
- the root
AppObjectnamedopen_client_sample, with:- non-persistent:
testextprog.p - persistent:
testargs.p,testtable.pandtestdataset.p
- non-persistent:
- a
SubAppObjectnamedsubtype, with:testpersistent.pran persistent
open_client_sample.javafor the rootAppObject, with:createAO_subtypeto create an instance for thesubtypeSubAppObjectcreatePO_testargs()for thetestargs.ppersistent proxycreatePO_testtable()for thetesttable.ppersistent proxycreatePO_testdataset()for thetestdataset.ppersistent proxyString testextprog(String p1, StringHolder p2)for thetestextprognon-persistent proxy
testargs.java, for thetestargs.pprogram, with:String test_func_input(...)method, for the internal functionString test_input(...)method, for the internal procedure
testpersistent.java, for thesubtype/testpersistent.pprogram, with:String test1(String p1, StringHolder p2, DateHolder p3), for the internal procedure
testtable.java, for thetesttable.pprogram, with:public String test_table_fields(ProDataGraphHolder tt1), for the internal procedure
testdataset.java, for thetestdataset.pprogram, with:public String test_dataset(ProDataGraphHolder ds2), for the internal procedure
subtype.java, with:testpersistent createPO_testpersistent(Integer i), forsubtype/testpersistent.p
- each .java file will have a constructor accepting a single
LegacyJavaAppserverClient clientargument, to create a new instance using the specified FWD client connection
open_client_sample.java, for the rootAppObject, with:public testargs new_testargs(), to create a helper instance to executetestargs.pLegacyJavaAppserverProxyInvocationResult testextprog(String p1, String p2), to execute thetestextprog.pprogramtesttable new_testtable(), to create a helper instance to executetesttable.ptestdataset new_testdataset(), to create a helper instance to executetestdataset.p
testargs.java, for thetestargs.pprogram, with:LegacyJavaAppserverProxyInvocationResult test_input(...), for the internal procedureLegacyJavaAppserverProxyInvocationResult test_func_input(...), for the internal function
testpersistent.java, for thesubtype/testpersistent.pprogram, with:LegacyJavaAppserverProxyInvocationResult runPersistent(Integer i), to run this program persistentLegacyJavaAppserverProxyInvocationResult test1(String p1, String p2, java.util.GregorianCalendar p3), for the internal procedure
testtable.java, for thetesttable.pprogram, with:public LegacyJavaAppserverProxyInvocationResult runPersistent(), to run the program persistentpublic LegacyJavaAppserverProxyInvocationResult test_table_fields(DataGraph tt1), to execute the internal procedurepublic static DataGraph create_tt1(), to create a data-graph for thett1temp-table.
testdataset.java, for thetestdataset.pprogram, with:public LegacyJavaAppserverProxyInvocationResult runPersistent(), to run the program persistentpublic LegacyJavaAppserverProxyInvocationResult test_dataset(DataGraph ds2), to execute the internal procedurepublic static DataGraph create_tt3(), to create a data-graph for thett3temp-table.public static DataGraph create_tt2(), to create a data-graph for thett2temp-table.public static DataGraph create_ds2(), to create a data-graph for theds2dataset.
subtype.java, for thesubtypeSubAppObject, with:public testpersistent new_testpersistent(), to create a helper instance to executesubtype/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
AppObjectorSubAppObjectinstance, which will:- return a
Stringvalue, for theRETURN-VALUEfrom the external program - return the instance associated for that proxy program, which will allow you to execute the internal function/procedures.
- return a
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 fwdconnection is assumed to be available - the
AppObjectinstance is created viaopen_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
- in case of programs ran persistent, after obtaining the proxy instance, the
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.
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
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 argumentInteger getIntOutputParameterInteger[] getIntArrayOutputParameterString getStringOutputParameterString[] getStringArrayOutputParameterDouble getDoubleOutputParameterDouble[] getDoubleArrayOutputParameterBigDecimal getBigDecimalOutputParameterBigDecimal[] getBigDecimalArrayOutputParameterBoolean getBooleanOutputParameterBoolean[] getBooleanArrayOutputParameterLong getLongOutputParameterLong[] getLongArrayOutputParameterGregorianCalendar getCalendarOutputParameterGregorianCalendar[] getCalendarArrayOutputParameterbyte[] getByteArrayOutputParameterbyte[][] getByteArrayArrayOutputParameterMemoryBuffer getMemoryBufferOutputParameterMemoryBuffer[] getMemoryBufferArrayOutputParameterDataGraph getDataSetOutputParameterDataGraph 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.
- create the
DataGraphfor a temp-tableProDataGraphMetaData tt1Param = new ProDataGraphMetaData(0, "tt1", 1, ParameterSet.INPUT_OUTPUT); tt1Param.addTable(tt1TableMetaData); ProDataGraph tt1 = new ProDataGraph(tt1Param); - create the
DataGraphfor 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.
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:secureforces the connection to be made via SSLnet:server:hostis the target FWD server's hostnet:server:secure_portis the target FWD server's secure portsecurity:keystore:processaliasis the alias of a process account configured on the FWD serversecurity:keystore:filenameis the JKS store file with the private key and certificate for this account (under theprocessaliasabove)access:password:keystoreis the password for the store fileaccess:password:keyentryis the password for the private-key entry in the store filesecurity:truststore:filenameis the store file which includes the FWD server's certificate and the root certificate (usually namedsrv-certs.store)security:truststore:aliasis 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:
trueif the target appserver isState-freefalseotherwise
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:secureforces the connection to be made insecurenet:server:hostis the target FWD server's hostnet:server:insecure_portis 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 callingsetDotNetCompatibility on the LegacyJavaAppserverClient instance obtained via connectRemote; this will:
- force all extent fields to be denormalized, so a field
foo[4]can be accessed viafoo1, foo2, foo3, foo4. - tracking-changes is enabled by default
- each
BigDecimalwill 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.