Proxygen, REST, SOAP and WebHandler¶
OpenClient Proxy Clients¶
Input Data¶
For generating the OpenClient proxy programs, FWD uses as input one or more .xpxg files, generated via the legacy proxygen tool, for Java or .NET Open Client.
These .xpxg files will reference 4GL programs, set to be ran non-persistent, persistent, single-run or singleton. These programs can be configured in one or more AppObject (a .xpxg file) or SubAppObject - a program can not be of the same AppObject or SubAppObject, part more than once.
Configuration:¶
In FWD, the configuration is done viap2j.cfg.xml parameters:
- specify a single
.xpxgfile or a folder where the.xpxgfiles reside, via theproxy-programs-cfgparameter. If a folder is specified, this will be read recursively. - specify the folder where the Java source code for the generated OpenClient proxies is emitted, via
proxy-output-rootparameter. - specify if the generated Java code will use legacy names (for external program, internal procedure, function, dataset, temp-tables, parameters), or the FWD naming will be used. This is done via the
proxy-legacy-names, which is optional, and defaults tofalse(the FWD naming convention will be used).
The Java source code will be emitted in the Java package as specified via the PGGenInfo/Package entry in the AppObject. If is missing, the code will be emitted in the proxy sub-package of the configured pkgroot parameter in p2j.cfg.xml.
A sample of these parameters is:
<parameter name="proxy-programs-cfg" value="abl/proxy"/> <!-- folder where the .xpxg exist -->
<parameter name="proxy-legacy-names" value="true"/> <!-- uses the legacy names for temp-tables, programs, internal procedures/functions -->
<parameter name="proxy-output-root" value="src" /> <!-- folder where to write the proxy programs -->
Conversion Process¶
FWD conversion is built to process 4GL programs or classes. As a 4GL program can be part of multiple (Sub)AppObject configurations, the processing is done in 'batches': each batch will process programs for only one (Sub)AppObject configuration, which will incrementally add its Java code to the JAST for that (Sub)AppObject.
This allows the same program, when is part of multiple (Sub)AppObject, to be emitted in each corresponding Java class for that (Sub)AppObject.
To generate the Java code for all 4GL proxy programs, it can be done:- using a full conversion
- using the
GenerateLegacyProxyOpenClient- this should be executed in its own conversion project, as it will invalidate any existing conversion in the project folder.
- use incremental conversion to convert the 4GL program
- use
GenerateLegacyProxyOpenClienttool to generate all the proxy programs, in a separate project folder
Both folder projects are assumed to contain the same 4GL sources.
For conversion, FWD does this in two phases.- first phase is to load the
.xpxgfiles and load each (Sub)AppObject intoLegacyProxyConfiginstances, with all the information needed about the 4GL programs exported as OpenClient proxies (each 4GL program referenced in a (Sub)AppObject will have aLegacyProxyClientConfiginstance, where all its details are kept). This loading is done viaServiceSupport.initializeProxyConfigurationsAPI, and uses as input the.xpxgfiles specified via theproxy-programs-cfgconfiguration parameter. The conversion requires only to annotate (viaannotations/proxy_programs.xml) the ASTs (the external program, temp-tables, datasets, procedures, functions, etc), with a specialproxy-client-programannotation, which will be later used to identify these structures, when the actual Java code for the OpenClient is generated. - the second phase is the actual generation of the Java OpenClient proxy code, where the responsible TRPL rule-set is
convert/proxy_programs.xml.
Both the above rule-sets rely on ServiceSupport.isLegacyProxyClient to identify if a file is exposed as a OpenClient proxy.
When GenerateLegacyProxyOpenClient tool is used, an additional TRPL rule-set is ran before the two TRPL rule-sets above, annotations/legacy_services.xml - this includes any TRPL rule-sets mandatory for the OpenClient proxy generation (like processing for procedures, functions, class methods, temp-tables, etc)
Proxy Program Structure¶
A Java class will be generated for each (Sub)AppObject. The Java class name will be either the name configured atAppObject/Name or SubAppObject/Name . Each Java class will contain:
- methods to run the external proxy program, with its parameters; the naming will be:
- for a persistent, singleton or single-run procedure, the program name, using the
new_prefix. - for a non-persistent procedure, the program name.
- for a persistent, singleton or single-run procedure, the program name, using the
- methods to create temp-table or dataset
DataGraphinstances, where required by the external program parameters.
Proc Object concept in OpenClient), which will contain:
- methods for each internal procedure or function
- methods to create temp-table or dataset
DataGraphinstances, where required by the internal procedure or function.
Each of the generated Java class (for (Sub)AppObject or for an external program ran persistent) will inherit the LegacyJavaAppserverClientProxy class.
- converted using the FWD converter (they will match the names in the associated 4GL program), when
proxy-legacy-namesparameter is missing or set tofalseincfg/p2j.cfg.xml. - using the exact legacy name, for the (Sub)AppObject, external program, internal procedures, dataset, temp-tables, etc, when
proxy-legacy-namesflag is set totrueincfg/p2j.cfg.xml. TODO: these names are not sanitized to be compatible as Java names, so if the legacy names contains hyphens or other illegal characters in Java names, the code will not compile. This will be fixed #6644.
Runtime Invocation¶
The execution of a Java Open Client proxy (be it a standalone external program invocation, a persistent/singleton/single-run invocation or an internal procedure/function call) starts with establishing a connection to the FWD server where the proxied 4GL programs are running; this is done using theLegacyJavaAppserverClient.connectRemote API call, with the following parameters:
BootstrapConfig cfg, with the settings to connect to the remote FWD server.String appserver, the target appserver serving the 4GL proxied programs.boolean sessionFree, flag indicating if the target appserver runs inSession-freemode.
An example of such configuration is:
BootstrapConfig cfg cfg = new BootstrapConfig();
cfg.setServer(false);
cfg.setConfigItem("net", "connection", "secure", "false");
cfg.setConfigItem("net", "server", "host", "localhost");
cfg.setConfigItem("net", "server", "insecure_port", "3433");
LegacyJavaAppserverClient fwd = LegacyJavaAppserverClient.connectRemote(cfg, "app_server", true);
The LegacyJavaAppserverClient instance will have a AppServerHelper helper instance, which is connected to the remote FWD server.
LegacyJavaAppserverClient instance, create a new instance for the (Sub)AppObject class associated with the desired 4GL proxied program, by passing the LegacyJavaAppserverClient instance to its constructor. All calls will be routed via LegacyJavaAppserverClient APIs to the target FWD server, via one of runFunction, runProcedure, runSingleRunProcedure, runSingletonProcedure, runPersistentProcedure APIs. The call flow is:
- the generated Java method for required target calls one of the
runAPIs in theLegacyJavaAppserverClientinstance. LegacyJavaAppserverClientdelegates the call to aAppServerHelper.invoke...method.- arguments are preprocessed via
AppServerHelper.preProcessParametersAppServerHelper.invoke...delegates the call to itsAppServerEntryinstance, which is a network proxy connected to the FWD server - on the FWD server side, there is an equivalent static Java method in theAppServerManagerclass.
AppServerHelper.checkResultswill raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments viapostProcessResultmethod.
- arguments are preprocessed via
Dispatcher threads, which will:
- execute the proper
AppServerManager.invoke...method. This will determine the proper FWD Agent to handle this request, post the work to the Agent (via one of itsinvoke...methods), and wait for a response - the Agent's
invoke...method will rely on one of theControlFlowOpsmethods to execute the request - the call is executed as any other external program/internal procedure or function invocation, from the converted code. - The response is returned back to the
AppServerHelpercaller, via theAppServerManager.invokemethod.
REST services¶
Input Data¶
Rest services are configured in OpenEdge via .paar files. This is a zip archive, which contains several files:spring.xml, from where FWD extracts only the service address. This address is a path, which will be added to the basepath configured in FWD for the REST services.resourceModel.xml, which defines all REST operations, with their path and verb.mapping.xml, used to extract the REST parameters (either at path, JSON, cookie, etc) for each REST operation.<serviceName>.restoe, used to map the 4GL programs with the REST operations
FWD parses these files and matches a REST operation with a 4GL program (or its internal procedures) on the PROPATH. To be considered for a match, the 4GL program or its internal procedures must have the 4GL openapi.openedge.export FILE(type="REST", ... annotation.
Configuration¶
In FWD, the rest-cfg configuration parameter in p2j.cfg.xml is used to specify either a single .paar file or a folder with multiple .paar files (which will be read recursively).
A sample of this parameters is:
<parameter name="rest-cfg" value="cfg/goldencodeService.paar" />
Conversion Process¶
When converting a 4GL application configured with REST services, FWD will identify all 4GL programs/classes (and its internal entries) exported as REST services. In each case, it will determine the REST configuration for it, and will enhance the openapi.openedge.export 4GL annotation directly at the program's AST, with the REST configuration.
The .paar input files are processed via ServiceSupport.resolveRestConfiguration, which creates data structures identifying the REST service (this includes the REST service name, path, verb, method, parameters). 4GL programs and its internal entries will map to a target REST service using its program name, and, in case of internal entries, the program name suffixed with the internal entry name, using .. as separator.
During conversion, these openapi.openedge.export annotations will be emitted as LegacyService Java annotations. During development process, REST services can be defined directly in the 4GL source code (FWD-only compatible), via annotations like these:
@LegacyService(type = "REST", name = "pipe_integer", path = "/pipe/integer/~{value~}", address = "/goldencodeService", verb = "GET", produces = "application/json", consumes = "application/json", parameters =
[
@LegacyServiceParameter(ordinal = 1, name = "inputValue", type = "integer", source = "$~{rest.pathparam['/pipe/integer/~{value~};value']~}", input = true),
@LegacyServiceParameter(ordinal = 2, name = "outputValue", type = "integer", target = "$~{json.object['response'].integervalue['outputValue']~}", output = true)
]).
This allows moving away from the
.paar configuration and having all necessary information directly in the 4GL code.
Any 4GL program which itself or one of its internal procedures/functions exported as a REST service will have added in its name_map.xml definition the with-services="true" flag, at the class-mapping node associated with the 4GL program.
The conversion process gathers data about the 4GL programs/internal entries exposed as REST services during annotations phase, via annotations/legacy_services.rules - this will process the openapi.openedge.export annotation, to be converted later as LegacyService Java annotation, and also expand it with LegacyServiceParameter annotation and other information. FWD will automatically emit these 4GL-like annotations as Java annotations.
REST services are generated either in full or incremental conversion, with no special management required. For incremental conversion, if a 4GL program configured for REST services is included, its LegacyService annotation will be automatically generated, the same way as when a full conversion is ran.
If any of the .paar files are changed, a full conversion must be executed.
annotations/legacy_services.rules, which uses:
ServiceSupport.isRestServiceto determine if a 4GL program or its internal entry map to a REST service- if the top-level block is marked with the 4GL
openapi.openedge.exportannotation, this will be expended to emit asLegacyServiceJava annotation, including any requiredLegacyServiceParameterannotations (for the parameters). Helper APIs are:ServiceSupport.resolveRestAnnotationto resolve theLegacyServiceannotation instance, which will be injected in the ASTServiceSupport.getParameters, to resolve theLegacyServiceParameterlist, which will be injected in the AST for theLegacyService.parametersannotation.
Generated Annotations¶
The REST conversion result areLegacyService annotations emitted in the Java code for the external program and/or its internal entries. An emitted annotation looks like this, for an external program:
@LegacyService(type = "REST", executionMode = "external", name = "pipe_integer", path = "/pipe/integer/{value}", address = "/goldencodeService", verb = "GET", produces = "application/json", consumes = "application/json", parameters =
{
@LegacyServiceParameter(ordinal = 1, name = "inputValue", type = "integer", source = "${rest.pathparam['/pipe/integer/{value};value']}", input = true),
@LegacyServiceParameter(ordinal = 2, name = "outputValue", type = "integer", target = "${json.object['response'].integervalue['outputValue']}", output = true)
})
where:
typeis always set to"REST"executionModecan be"external","singleton","single-run"or"persistent".nameis the REST service operation name.addressis the REST service address, relative tobasepathconfigured for the FWD server's REST handler.pathis the REST service path, relative to theaddressverbis the HTTP method (GET, POST, DELETE, etc).consumesandproducesis the Content-type for the request and response.parametersis a list ofLegacyServiceParameterannotation, a one-to-one mapping of the REST parameters with the 4GL programs or internal entry's signature. Each annotation must provide:- parameter's
ordinal(in the list of the target's 4GL parameters).ordinal = 0maps a REST parameter with a function's return value. - parameter's
type(a 4GL data type). - parameter's mode, via
inputandoutputflags - this allows mapping a parameter asINPUT,OUTPUTorINPUT-OUTPUT. returnValue = true, only ifordinal = 0, to specify a function's return type.- parameter's
source, mandatory if if the parameter hasinput = true. - parameter's
target, mandatory if if the parameter hasoutput = trueorreturnValue = true.
- parameter's
FWD will automatically extract the parameters from the REST request, populate the arguments using FWD compatible data-types (this includes DATASET, TABLE, etc), and also automatically populate the response by serializing the OUTPUT or RETURN parameters to their corresponding target.
Runtime Invocation¶
At FWD server startup, it will go through all programs configured in name_map.xml having with-services="true" attribute, and will check their LegacyService annotation - if type = "REST", it will automatically configure the REST service and map it to the program or internal entry. Context handlers will be created for each defined address, and a handler for a certain address will process all requests for the REST services configured for it. All context handlers will have as handler the singleton RestHandler instance.
The last step for initializing the FWD REST service support is to launch a dedicated pool of LegacyServiceWorker worker threads (via AppServerConnectionPool), which only handle REST requests: each worker thread will be connected to the appserver (running in the same FWD server) responsible for performing the actual execution of the REST call's target. Each LegacyServiceWorker worker will have a AppServerHelper instance, connected via a local proxy to the target appserver (running in the same JVM).
RestHandler.handle API is responsible for interpreting it; if it finds a RestService mapping for that target and verb combination, it will post a task to a worker, via a RestHandler.invoke job, and wait for its completion. The worker will:
- parse the arguments from the REST request and create compatible instances with the target parameters (at the 4GL program or internal entry).
- use the
RestServiceinstance to determine the properAppServerHelperAPI to perform the remote call (i.e. invoke a 4GL program, an internal entry, etc), so it can delegate the call to aAppServerHelper.invoke...method, where:- arguments are preprocessed via
AppServerHelper.preProcessParametersAppServerHelper.invoke...delegates the call to itsAppServerEntryinstance, which is a local proxy for theAppServerManagerstatic methods.
AppServerHelper.checkResultswill raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments viapostProcessResultmethod.
- arguments are preprocessed via
- the worker waits for the result from the
AppServerHelper.invokecall - once the result is received, the worker will serialize the response to the proper format/target (response body, cookie, header, etc).
When control is received back by the web thread handling the REST request, the worker thread will have finished the request invocation and processing of the HTTP response.
On server side, the remote request fromRestHandler is received by one of the Dispatcher threads, which will:
- execute the proper
AppServerManager.invoke...method. This will determine the proper FWD Agent to handle this request, post the work to the Agent (via one of itsinvoke...methods), and wait for a response - the Agent's
invoke...method will rely on one of theControlFlowOpsmethods to execute the request - the call is executed as any other external program/internal procedure or function invocation, from the converted code. - The response is returned back to the
AppServerHelpercaller, via theAppServerManager.invokemethod.
SOAP Services¶
Input Data¶
For SOAP services, FWD uses as input one or more .wsm files, with the configuration for each 4GL program and internal entry exposed as a SOAP operation. FWD will process all these files, automatically configure the 4GL program/internal entry as a SOAP operation, create the mappings and schema for the parameters, messages, bindings and port type, and generate the WSDL.
The result will be annotations at the converted Java code for the 4GL programs/internal entries and also .wsdl files for each .wsm file used as input.
Configuration¶
In FWD, thesoap-cfg configuration parameter in p2j.cfg.xml is used to specify either a comma-separated list of:
.wsmfiles- folders (read recursively) containing
.wsmfiles.
A sample of this parameters is:
<parameter name="soap-cfg" value="cfg/fwd.wsm" />
Conversion Process¶
When converting a 4GL application configured with SOAP, FWD will identify all 4GL programs (and its internal entries) exported as SOAP operations, from the .wsm file. For each program or internal entry exposed as a SOAP operation, it will emit a LegacyService Java annotation.
All the .wsm file information is loaded during conversion via ServiceSupport.createSoapConfig - this creates SoapConfig instances for each .wsm file, with SoapOperation instances for each SOAP operation.
For each namespace configured in the .wsm file at DeploymentWizard/WebServiceNamespace, a WSDL file will be generated defining all SOAP operations in that namespace.
Any 4GL program which itself or one of its internal procedures/functions exported as a SOAP service will have added in its name_map.xml definition the with-services="true" flag, at the class-mapping node associated with the 4GL program.
When there are SOAP services configured at conversion, WSDL generation requires all programs/classes marked for SOAP services to be processed, regardless if this is a full or incremental conversion (for incremental conversion, only this phase will include all 4GL programs/classes configured for SOAP).
Otherwise, for incremental conversion, any 4GL program/class configured for SOAP will have its Java annotation emitted. If any .wsm file is changed, a full conversion is required.
annotations/legacy_services.rules, which:
- uses
ServiceSupport.getSoapOperations, to determine the entire list of SOAP operations which are associated with this top-level block (4GL program, internal procedure, function, etc). - injects a
LegacyServiceannotation for each SOAP operation, with itsnamespace,bindingand operationname.
convert/soap_services.xml, with:
ServiceSupport.getSoapOperationsdetermines all SOAP operations associated with a top-level block (external program, internal procedure, etc).ServiceSupport.addSoapTableParameterwill register with the WSDL atableparameter for that SOAP operation.ServiceSupport.addSoapDataSetParameterwill register with the WSDL adatasetparameter for that SOAP operation.ServiceSupport.addSoapParameterwill register with the WSDL a normal parameter for that SOAP operation.ServiceSupport.setSoapReturnTypewill register with the WSDL for that SOAP operation the function's return type.
The internal representation (at conversion) of the WSDL is done via SoapWsdl instances, for each SoapConfig instance associated with a .wsm file. Once all SOAP-related files are processed by convert/soap_services.xml, it will call ServiceSupport.generateSoapWsdl, which will write the WSDL files to disk, in the root package configured via pkgroot parameter in p2j.cfg.xml.
Generated Annotations/WSDL¶
Each 4GL program, class or internal entry will have aLegacyService annotation emitted, like this:@LegacyService(type = "SOAP", namespace = "urn:tempuri-org", name = "pipe_integer", binding = "fwd")
where:
type = "SOAP"marks it as a SOAP operationnamespacedefines the namespace (the target WSDL) for this operation- the operation
name - the WSDL
bindingdescribing the SOAP operation messages (how the request and response are mapped to the 4GL parameters).
The WSDL files are generated for each namespace (configured in the .wsm file(s)); these WSDL files are generated in the pkgroot folder, and must be included in the application jar, during build.
Runtime Invocation¶
At FWD server startup, it will go through all programs configured in name_map.xml having with-services="true" attribute, and will check their LegacyService annotation - if type = "SOAP", it will automatically configure this program, class or internal entry as a SOAP operation.
Context handlers will be created for each defined service/port/address in the WSDL files, and a handler for a certain endpoint will process all requests for the SOAP operations configured for it. All context handlers will have as handler a SoapHandler instance, in FWD. One context handler (configured for an endpoint) can service SOAP operations defined in one or more WSDLs.
The last step for initializing the FWD SOAP service support is to launch a dedicated pool of LegacyServiceWorker worker threads (via AppServerConnectionPool), which only handle SOAP requests: each worker thread will be connected to the appserver (running in the same FWD server) responsible for performing the actual execution of SOAP call's target. Each LegacyServiceWorker worker will have a AppServerHelper instance, connected via a local proxy to the target appserver (running in the same JVM).
SoapHandler.handle API is responsible for interpreting it; it will look at the SOAP envelope, and will:
- use the namespace to find the target WSDL definition this SOAP operation.
- look through the WSDL operations and find the target 4GL program, class or internal entry, for the SOAP operation.
SoapHandler.invoke job, and wait for its completion. The worker will:
- parse the arguments from the SOAP envelope and create compatible instances with the target parameters (at the 4GL program or internal entry).
- use the SOAP service type to determine the proper
AppServerHelperAPI to perform the remote call (i.e. invoke a 4GL program, an internal entry, etc), so it can delegate the call to aAppServerHelper.invoke...method, where:- arguments are preprocessed via
AppServerHelper.preProcessParametersAppServerHelper.invoke...delegates the call to itsAppServerEntryinstance, which is a local proxy for theAppServerManagerstatic methods.
AppServerHelper.checkResultswill raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments viapostProcessResultmethod.
- arguments are preprocessed via
- the worker waits for the result from the
AppServerHelper.invokecall - once the result is received, the worker will serialize the OUTPUT parameters in the SOAP response
When control is received back by the web thread handling the SOAP request, the worker thread will have finished the request invocation and processing of the HTTP response.
On server side, the remote request fromSoapHandler is received by one of the Dispatcher threads, which will:
- execute the proper
AppServerManager.invoke...method. This will determine the proper FWD Agent to handle this request, post the work to the Agent (via one of itsinvoke...methods), and wait for a response - the Agent's
invoke...method will rely on one of theControlFlowOpsmethods to execute the request - the call is executed as any other external program/internal procedure or function invocation, from the converted code. - The response is returned back to the
AppServerHelpercaller, via theAppServerManager.invokemethod.
WebHandler¶
Input Data¶
In 4GL, web handlers are configured in theopenedge.properties file. From this file, all sections with the .WEB suffix are required:
[oepas1.goldencode.WEB]
handler1=FwdHandler: /fwdHandler/{id}/{value}
handler2=FwdHandler: /fwdHandler/{id}
handler3=FwdHandler: /fwdHandler
where the syntax of a handler is:
handler#=<4GLClassName>: /path1/path2/.../
and:
handler#specifies the priority of this path<4GLClassName>specifies a 4GL class inheritingOpenEdge.Web.WebHandler/path1/path2/.../specifies the path handled by this handler
This configuration will need to be added to FWD server's directory.xml - see Runtime_Configuration_for_REST_SOAP_and_WEB for details.
Conversion Process¶
The conversion will not generate anything special for legacy /web services.
Runtime Invocation¶
At FWD server startup, it will go through the webHandler configuration from the FWD directory, where it specifies the default or multiple web apps (deployed on their own sub-basepath). The handlers for each webapp are read and registered, using the 4GL class name of each handler, in order. All legacy web services will be handled in FWD by the WebServiceHandler class.
The last step for initializing the FWD legacy web service support is to launch a dedicated pool of LegacyServiceWorker worker threads (via AppServerConnectionPool), which only handle REST requests: each worker thread will be connected to the appserver (running in the same FWD server) responsible for performing the actual execution of the web service call's target. Each LegacyServiceWorker worker will have a AppServerHelper instance, connected via a local proxy to the target appserver (running in the same JVM).
WebServiceHandler.handle API is responsible for interpreting it; if it finds a registered WebHandler class for that path, it will post a task to a worker, and wait for its completion. The worker will:
- use
AppServerHelper.handleWebServiceAPI to perform the remote call - the target legacy 4GL converted class will be included as a parameter.AppServerHelper.handleWebServicedelegates the call to itsAppServerEntryinstance, which is a local proxy for theAppServerManager.handleWebServicestatic method.- the remote
handleWebServicecall will receive theHttpServletRequestandHttpServletResponseJava instances.
- the worker waits for the result from the
AppServerHelper.handleWebServicecall - once the result is received, the worker will serialize the response to the proper format/target (response body, cookie, header, etc).
When control is received back by the web thread handling the web request, the appserver Agent will have finished the request invocation and processing of the HTTP response.
On server side, the remote request fromWebServiceHandler is received by one of the Dispatcher threads, which will:
- execute the
AppServerManager.handleWebServicemethod. This will determine the proper FWD Agent to handle this request, post the work to the Agent (via onehandleWebServicemethod), and wait for a response. - the Agent's
handleWebServicemethod will:- initialize the FWD implementation of
OpenEdge.Web.WebRequestto have access to theHttpServletResponseandHttpServletResponseinstances. - create a new instance of the
WebHandlerimplementation class and invoke itshandleRequestmethod.
- initialize the FWD implementation of
© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.