Project

General

Profile

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 via p2j.cfg.xml parameters:
  • specify a single .xpxg file or a folder where the .xpxg files reside, via the proxy-programs-cfg parameter. 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-root parameter.
  • 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 to false (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.
At this time, incremental conversion is not possible for proxy programs. It is recommended:
  • use incremental conversion to convert the 4GL program
  • use GenerateLegacyProxyOpenClient tool 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 .xpxg files and load each (Sub)AppObject into LegacyProxyConfig instances, with all the information needed about the 4GL programs exported as OpenClient proxies (each 4GL program referenced in a (Sub)AppObject will have a LegacyProxyClientConfig instance, where all its details are kept). This loading is done via ServiceSupport.initializeProxyConfigurations API, and uses as input the .xpxg files specified via the proxy-programs-cfg configuration parameter. The conversion requires only to annotate (via annotations/proxy_programs.xml) the ASTs (the external program, temp-tables, datasets, procedures, functions, etc), with a special proxy-client-program annotation, 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 at AppObject/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.
  • methods to create temp-table or dataset DataGraph instances, where required by the external program parameters.
For each external program ran persistent, singleton or single-run, a Java class will be generated (the Proc Object concept in OpenClient), which will contain:
  • methods for each internal procedure or function
  • methods to create temp-table or dataset DataGraph instances, 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.

When generating the Java code for the OpenClient proxy, the names will be:
  • converted using the FWD converter (they will match the names in the associated 4GL program), when proxy-legacy-names parameter is missing or set to false in cfg/p2j.cfg.xml.
  • using the exact legacy name, for the (Sub)AppObject, external program, internal procedures, dataset, temp-tables, etc, when proxy-legacy-names flag is set to true in cfg/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 the LegacyJavaAppserverClient.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 in Session-free mode.

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.

Once the connection was established via the 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 run APIs in the LegacyJavaAppserverClient instance.
  • LegacyJavaAppserverClient delegates the call to a AppServerHelper.invoke... method.
    • arguments are preprocessed via AppServerHelper.preProcessParameters
      • AppServerHelper.invoke... delegates the call to its AppServerEntry instance, which is a network proxy connected to the FWD server - on the FWD server side, there is an equivalent static Java method in the AppServerManager class.
    • AppServerHelper.checkResults will raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments via postProcessResult method.
On server side, the remote request from the OpenClient client 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 its invoke... methods), and wait for a response
  • the Agent's invoke... method will rely on one of the ControlFlowOps methods 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 AppServerHelper caller, via the AppServerManager.invoke method.

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.

For conversion, FWD relies on injecting 4GL-style annotations directly into the AST, which will later automatically convert as Java annotations. This is done via annotations/legacy_services.rules, which uses:
  • ServiceSupport.isRestService to 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.export annotation, this will be expended to emit as LegacyService Java annotation, including any required LegacyServiceParameter annotations (for the parameters). Helper APIs are:
    • ServiceSupport.resolveRestAnnotation to resolve the LegacyService annotation instance, which will be injected in the AST
    • ServiceSupport.getParameters, to resolve the LegacyServiceParameter list, which will be injected in the AST for the LegacyService.parameters annotation.

Generated Annotations

The REST conversion result are LegacyService 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:
  • type is always set to "REST"
  • executionMode can be "external", "singleton", "single-run" or "persistent".
  • name is the REST service operation name.
  • address is the REST service address, relative to basepath configured for the FWD server's REST handler.
  • path is the REST service path, relative to the address
  • verb is the HTTP method (GET, POST, DELETE, etc).
  • consumes and produces is the Content-type for the request and response.
  • parameters is a list of LegacyServiceParameter annotation, 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 = 0 maps a REST parameter with a function's return value.
    • parameter's type (a 4GL data type).
    • parameter's mode, via input and output flags - this allows mapping a parameter as INPUT, OUTPUT or INPUT-OUTPUT.
    • returnValue = true, only if ordinal = 0, to specify a function's return type.
    • parameter's source, mandatory if if the parameter has input = true.
    • parameter's target, mandatory if if the parameter has output = true or returnValue = true.

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).

When executing a REST request, the 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 RestService instance to determine the proper AppServerHelper API to perform the remote call (i.e. invoke a 4GL program, an internal entry, etc), so it can delegate the call to a AppServerHelper.invoke... method, where:
    • arguments are preprocessed via AppServerHelper.preProcessParameters
      • AppServerHelper.invoke... delegates the call to its AppServerEntry instance, which is a local proxy for the AppServerManager static methods.
    • AppServerHelper.checkResults will raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments via postProcessResult method.
  • the worker waits for the result from the AppServerHelper.invoke call
  • 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 from RestHandler 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 its invoke... methods), and wait for a response
  • the Agent's invoke... method will rely on one of the ControlFlowOps methods 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 AppServerHelper caller, via the AppServerManager.invoke method.

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, the soap-cfg configuration parameter in p2j.cfg.xml is used to specify either a comma-separated list of:
  • .wsm files
  • folders (read recursively) containing .wsm files.

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.

For conversion, FWD relies on injecting 4GL-style annotations directly into the AST, which will later automatically convert as Java annotations. This is done via 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 LegacyService annotation for each SOAP operation, with its namespace, binding and operation name.
For WSDL generation, this is done using all the 4GL programs which are associated with a SOAP operation, regardless if conversion is ran incremental or full. This is required because the WSDL can not be incrementally generated, and must contain details about all the SOAP operation details - their parameters, bindings, schema, port-type, etc. The information required for each WSDL is done using convert/soap_services.xml, with:
  • ServiceSupport.getSoapOperations determines all SOAP operations associated with a top-level block (external program, internal procedure, etc).
  • ServiceSupport.addSoapTableParameter will register with the WSDL a table parameter for that SOAP operation.
  • ServiceSupport.addSoapDataSetParameter will register with the WSDL a dataset parameter for that SOAP operation.
  • ServiceSupport.addSoapParameter will register with the WSDL a normal parameter for that SOAP operation.
  • ServiceSupport.setSoapReturnType will 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 a LegacyService annotation emitted, like this:
@LegacyService(type = "SOAP", namespace = "urn:tempuri-org", name = "pipe_integer", binding = "fwd")

where:
  • type = "SOAP" marks it as a SOAP operation
  • namespace defines the namespace (the target WSDL) for this operation
  • the operation name
  • the WSDL binding describing 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).

When executing a SOAP operation, the 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.
If it finds a mapping for that SOAP operation, it will post a task to a worker, via a 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 AppServerHelper API to perform the remote call (i.e. invoke a 4GL program, an internal entry, etc), so it can delegate the call to a AppServerHelper.invoke... method, where:
    • arguments are preprocessed via AppServerHelper.preProcessParameters
      • AppServerHelper.invoke... delegates the call to its AppServerEntry instance, which is a local proxy for the AppServerManager static methods.
    • AppServerHelper.checkResults will raise any exception as received from the remote side, and will post-process any OUTPUT/INPUT-OUTPUT arguments via postProcessResult method.
  • the worker waits for the result from the AppServerHelper.invoke call
  • 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 from SoapHandler 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 its invoke... methods), and wait for a response
  • the Agent's invoke... method will rely on one of the ControlFlowOps methods 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 AppServerHelper caller, via the AppServerManager.invoke method.

WebHandler

Input Data

In 4GL, web handlers are configured in the openedge.properties file. From this file, all sections with the .WEB suffix are read:
[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 inheriting OpenEdge.Web.WebHandler
  • /path1/path2/.../ specifies the path handled by this handler

Configuration

FWD will use as input a single openedge.properties file, specified via the webhandler-cfg parameter in p2j.cfg.xml:

      <parameter name="webhandler-cfg"    value="cfg/openedge.properties" />

Conversion Process

The information about the exposed web handler classes is resolved via ServiceSupport.resolveWebHandlers. This creates a WebHandlerConfig instance for each web handler, with its details (path, property section from where it was loaded, 4GL class, etc).

All 4GL classes specified as 'web handlers' will be identified during annotations, and each class AST will be added a 4GL-style annotation to idenfy this as a WEB handler, with its target paths. In 4GL, this annotation would look like this:

   @LegacyService(type = "WEBHANDLER", name = "oepas1.goldencode.web", paths =
   [
      @LegacyWebPath(order = 1, path = "/fwdHandler/{id}/{value}"),
      @LegacyWebPath(order = 2, path = "/fwdHandler/{id}"),
      @LegacyWebPath(order = 3, path = "/fwdHandler")
   ]).

This 4GL annotation can be added explicitly to any 4GL class inheriting OpenEdge.Web.WebHandler, and will be automatically converted to a Java annotation, by the FWD conversion.

Any incremental conversion whichi includes a WebHandler class will automatically generate the proper annotation. Any changes in the configured .properties file related to the WEB sections will require a full conversion.

For conversion, FWD relies on injecting 4GL-style annotations directly into the AST, which will later automatically convert as Java annotations. This is done via annotations/legacy_services.rules, which uses:
  • ServiceSupport.isWebHandler to determine if a 4GL class is exposed as a web handler.
  • if isWebHandler is true, a new LegacyService 4GL-style annotation will be injected directly into the AST for this 4GL class, with the web handler's details. Helper APIs are:
    • ServiceSupport.getSection, to resolve the property file section where this web handler was defined, and emit LegacyService.section annotation.
    • ServiceSupport.getPaths, to resolve the paths handled by this web handler, and emit LegacyService.paths annotation.

Generated Annotations

Any WebHandler sub-class which is configured for web services in the openedge.properties file will automatically be added a LegacyService Java annotation, like this:
@LegacyService(name = "oepas1.goldencode.web", type = "WEBHANDLER", paths = 
{
   @LegacyWebPath(path = "/fwdHandler/{id}/{value}", order = 1),
   @LegacyWebPath(path = "/fwdHandler/{id}", order = 2),
   @LegacyWebPath(path = "/fwdHandler", order = 3)
})

where:
  • type = "WEBHANDLER" represents a legacy web service
  • name represents the name of the section in the .properties file where this was configured
  • paths represents the handled paths by this class, via LegacyWebPath annotations, with:
    • order processing order.
    • path the handled path.

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 = "WEBHANDLER", it will automatically configure the legacy web service and map it to the WebHandler implementation class. 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).

When executing a web service request, the 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.handleWebService API to perform the remote call - the target legacy 4GL converted class will be included as a parameter.
    • AppServerHelper.handleWebService delegates the call to its AppServerEntry instance, which is a local proxy for the AppServerManager.handleWebService static method.
    • the remote handleWebService call will receive the HttpServletRequest and HttpServletResponse Java instances.
  • the worker waits for the result from the AppServerHelper.handleWebService call
  • 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 from WebServiceHandler is received by one of the Dispatcher threads, which will:
  • execute the AppServerManager.handleWebService method. This will determine the proper FWD Agent to handle this request, post the work to the Agent (via one handleWebService method), and wait for a response.
  • the Agent's handleWebService method will:
    • initialize the FWD implementation of OpenEdge.Web.WebRequest to have access to the HttpServletResponse and HttpServletResponse instances.
    • create a new instance of the WebHandler implementation class and invoke its handleRequest method.

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