The Progress OpenEdge.Web.DataObjectHandler (DOH) is a built-in WebHandler class that enables you to design your own ABL service interface and expose ABL data objects as RESTful resources in a configurable way. It is available via the WEB transport in Progress Application Server for OpenEdge (PAS for OpenEdge).

If you have developed Progress ABL Data Object Services, you have already used the DOH under the hood.

How the DOH works

The DOH class implements the Progress.Web.IWebHandler interface, and is a more dynamic and business-ready form of the OpenEdge.Web.WebHandler class. With the DOH, you create an ABL service, set it to use the OpenEdge.Web.DataObjectHandler class, and then map custom service endpoints to ABL classes and procedures in a JSON mapping file.

At runtime, when a service endpoint is called, the DOH transforms the HTTP request into an ABL object and invokes the associated ABL class or procedure. It also transforms the output from the ABL class or procedure into an HTTP response.

Should I use a user-defined WebHandler, the DOH or a mapped REST service?

Although you can also define a custom service interface using a mapped REST service (that uses the PAS for OpenEdge REST transport) or a user-defined WebHandler, a DOH-based ABL service offers the following advantages over the mapped REST service:

  • It supports more content types.
  • It is easier to debug, since the DOH is an ABL class. You can also readily inspect and modify its JSON mapping file.
  • It is more customizable; you can extend its functionality by accessing its class events. To learn more about these class events, see the white paper on using the DataObjectHandler.
  • It uses the WEB transport, which is the recommended way to expose your ABL applications as RESTful services. This is because the WEB transport uses the ABL WebHandler class and thus provides better insight and control over your ABL service.

Overview of steps to use the DOH

  1. Create an ABL service — You begin by creating an ABL service that uses the DOH class.
  2. Create a mapping file — You then create a mapping file with the same name as the ABL service.
  3. Define custom service endpoints — You start describing the service interface in the mapping file by defining your custom endpoints.
  4. Associate HTTP verbs with operation handlers — For each service endpoint, you associate an HTTP verb (GET, PUT, POST, etc) with one of the following operation handlers:
    • void, that returns only a status code
    • file, that returns the contents of a file
    • entity, that invokes an ABL class or procedure
  5. (Optional) Add mapping metadata for OpenAPI output — Add descriptions, access control, and service-level schemas in the mapping file so generated OpenAPI output is clearer and more complete.

Create an ABL service

The first step is to create an ABL service that uses the DOH. Perform this task as follows:

  1. Right-click the root project name in the Project Explorer view in Progress Developer Studio and select New > ABL Service.
  2. Select WEB in the Transport drop-down.
  3. Enter a name for the service, (such as HelloService).
  4. In the WebHandler section, choose Select existing and then browse and select OpenEdge.Web.DataObject.DataObjectHandler.
  5. Add a single resource URI that matches the service name (/HelloService).
  6. Click Finish.

Create a mapping file

After creating a DOH-based ABL service, you describe its service interface in JSON in a mapping file with the extension .map. The DOH uses this mapping file to execute the service's operations. If a client application attempts to call an endpoint or service that is not described in the mapping file, the DOH returns a 404 (Not Found) error.

Create a new text file in your ABL web app project's /PASOEContent/WEB-INF/openedge folder with the same name as the ABL service and with the extension .map (for example, HelloService.map). The matching filename instructs the DOH to use the mapping file for the service's operations. Placing it in the openedge folder makes it discoverable in the application PROPATH.

At minimum, you need to define a single service within a services object, like this:

{    "services":
      {
        "HelloService": {           
        "version": "1.0.0"       
      }
    }
 }

The service name must match the ABL service and resource URI name that you set while creating the ABL service. The "version" property defines the version of the service and should follow the Semantic Versioning pattern (for example,. "1.0.0"). The JSON snippet, as shown in this example, provides just enough to make the service discoverable but does not do much else. Without this minimum configuration, the server would only return an HTTP-404 response for the service.

Note: Mapping files can now carry richer metadata used by OpenAPI output, including descriptions, access control roles, and schema details.

Define custom service endpoints

In the JSON mapping file, define custom service endpoints under an operations object. For example:

{
	"services": {
		"HelloService": {
			"version": "1.0.0",
			"operations": {
				"/Greeting": {},
				"/Message": {},
				"/": {}
			}
		}
	}
}
This registers the endpoints with the DOH class and makes them accessible as REST resources. Client applications access these resources using the full endpoint URI. For example:
  • http://localhost/CustomerApp/web/HelloService/Greeting
  • http://localhost/CustomerApp/web/HelloService/Message

Associate HTTP verbs with operation handlers

For each custom service endpoint, you associate HTTP verbs with operation handlers. You can set up operation handlers for all HTTP verbs that are supported by the WEB transport. In addition to request handling, each verb definition can include metadata such as descriptions, access control roles, and schema details, which improves generated OpenAPI Specification output.

Supported HTTP verbs are:

  • GET
  • PUT
  • POST
  • DELETE
  • HEAD
  • OPTIONS
  • TRACE
  • PATCH

Specify each verb that you want to use as a JSON object under the service endpoint in the mapping file. For example:

{
	"services": {
		"HelloService": {
			"version": "1.0.0",
			"operations": {
				"/Greeting": {
					"GET": {},
					"PUT": {},
					"POST": {}
				},
				"/Message": {
					"GET": {},
					"PUT": {}
				},
				"/": {
					"GET": {}
				}
			}
		}
	}
}
Content types

Within each verb, define the MIME content type of the data that is returned by the endpoint. For example:

"/Greeting": {
	"GET": {
		"contentType": "application/json"
	}
}

All MIME types (text/, image/, audio/, video/, application/, multipart/) are supported.

Status codes
In addition to the content type, define the status code that is returned by the endpoint. For example:
"/Greeting": {
	"GET": {
		"contentType": "application/json",
		"statusCode": 200
	}
}
Operation handlers

Next, map each HTTP verb with one operation handler. The DOH class provides the following operation handlers:

  • void, which returns only a status code
  • file, which returns the contents of a file
  • entity, which invokes an ABL class or procedure

To map an HTTP verb with an operation handler, nest the operation handler under the verb as shown in this example:

"/Greeting": {
	"GET": {
		"contentType": "application/json",
		"entity": {}
	},
	"PUT": {
		"contentType": "application/json",
		"entity": {}
	}
}
"/Message": {
	"GET": {
		"contentType": "application/json",
		"file": "filepath"
	}
}
"/": {
	"GET": {
		"contentType": "application/json",
		"void": null
	}
}
The file and void operation handlers are simple key-value pairs whereas the entity handler is a JSON object. This is because the entity handler requires further information (such as the ABL class or procedure name, the method that will be invoked, etc).

Add mapping metadata for OpenAPI output

Data Object Handler mapping files can include metadata that improves the OpenAPI output generated from your service definitions. In addition to routing and handler selection, you can describe services, operations, and parameters, and provide security and schema information that the OpenAPI writer can use when it builds the specification.

Note: The metadata properties described in this section apply only to .map files. If you use Progress Developer Studio for OpenEdge to generate a .gen file from your ABL services, rename it to .map before adding custom metadata. Regenerating the .gen file from Progress Developer Studio overwrites any manual edits, so working with a .map file ensures your descriptions, access control, and schema definitions are preserved.
Tip: You can validate the syntax of your mapping file against the official JSON Schema definition published in the Progress ADE repository at https://github.com/progress/ADE/blob/release-12.8.x/netlib/OpenEdge/Web/DataObject/operationmap.schema.json. This schema (version 1.1.0) describes all supported mapping properties, including the metadata fields documented in this section. Referencing this file in your integrated development environment (IDE) or editor enables auto completion and real-time validation as you author .map files.

The most useful properties are:

  • description: Adds customer-facing text for a service, operation, or parameter. Use it at the service level or within each HTTP verb object. (See the "Description property in mapping metadata" section below for more detail.)
  • accessControl: Specifies one or more roles required to invoke an operation. Define it as an array within each HTTP verb object. (See the "Access control in mapping metadata" section below for more detail.)
  • schemas: Defines service-level JSON Schema content that can be used to describe request and response bodies in generated OpenAPI output. (See the "Service-level JSON Schema" section below for more detail.)

Example:

{
  "services": {
    "OrderService": {
      "version": "1.0.0",
      "description": "Operations for querying and managing customer orders.",
      "operations": {
        "\/order\/{id}\/status": {
          "GET": {
            "contentType": "application\/json",
            "statusCode": 200,
            "description": "Retrieve the current status of a single order by ID.",
            "accessControl": [
              "ROLE_USER",
              "ROLE_ADMIN"
            ],
            "entity": {
              "name": "MyApp.OrderService",
              "function": "GetOrderStatus",
              "description": "Returns the current status of an order given its ID",
              "arg": [
                {
                  "ablName": "piOrderID",
                  "ablType": "integer",
                  "ioMode": "INPUT",
                  "description": "The numeric ID of the order to retrieve",
                  "msgElem": {
                    "type": "path",
                    "name": "id"
                  }
                },
                {
                  "ablName": "poOrderStatus",
                  "ablType": "CLASS Progress.Json.ObjectModel.JsonObject",
                  "ioMode": "OUTPUT",
                  "description": "JSON object containing the order ID and current status",
                  "msgElem": {
                    "type": "body"
                  }
                }
              ]
            }
          }
        }
      },
      "schemas": {
        "poOrderStatus": {
          "type": "object",
          "description": "An order with its current status",
          "properties": {
            "id": {
              "type": "string",
              "description": "Unique identifier of the order"
            },
            "status": {
              "type": "string",
              "description": "Current status of the order"
            }
          },
          "required": ["id", "status"]
        }
      }
    }
  }
}
Note: OpenEdge provides three OpenAPI service writer classes, each targeting a different specification version:
Class OAS version
OpenEdge.Web.DataObject.Writer.OpenAPI30ServiceWriter
3.0.x
OpenEdge.Web.DataObject.Writer.OpenAPI31ServiceWriter
3.1.x
OpenEdge.Web.DataObject.Writer.OpenAPI32ServiceWriter
3.2.x
All three implement the IOpenAPIServiceWriter interface. The default writer used by the CatalogWebHandler class is OpenAPI31ServiceWriter. Choose the writer version that matches the tooling requirements of your API consumers.

After you update the mapping file, regenerate the OpenAPI output and review it to confirm that descriptions, security expectations, and schema details match runtime behavior. For more information, see ABL application catalog service in Manage Progress Application Server (PAS) for OpenEdge.

Description property in mapping metadata

You can add a description string at multiple levels throughout the mapping file. Each description flows into the corresponding location in the generated OpenAPI specification:

Table 1. Description properties
Level Where to place it What it describes in the OpenAPI
Service Directly in the service object The info.description for the API
Operation Inside each HTTP verb object (for example, "GET": { "description": "..." }) The operation summary or description
Entity Inside the entity object for a verb A description of the underlying handler or business logic
Parameter Inside each argument definition in the verb's parameter list The description field on an OpenAPI parameter
Schema element Inside a JSON Schema property definition within the schemas object The description on a schema property
ProDataset / Temp-Table field In the field definitions of a dataset or temp-table schema The description on the corresponding schema property

Example showing descriptions at service, operation, and parameter levels:

{
    "services": {
        "OrderService": {
            "version": "1.0.0",
            "description": "Operations for querying and managing customer orders.",
            "operations": {
                "/orders/{id}": {
                    "GET": {
                        "description": "Retrieve a single order by ID.",
                        "contentType": "application/json",
                        "statusCode": 200,
                        "entity": {
                            "name": "MyApp.OrderService",
                            "function": "GetOrderById",
                            "description": "Invokes the order lookup handler.",
                            "arg": [
                                {
                                    "ablName": "id",
                                    "ablType": "CHARACTER",
                                    "ioMode": "INPUT",
                                    "msgElem": {
                                        "type": "path",
                                        "name": "id"
                                    },
                                    "description": "The unique identifier of the order to retrieve."
                                }
                            ]
                        }
                    }
                }
            }
        }
    }
}

All description properties are optional and backward compatible. Omitting them has no effect on service operation.

Access control in mapping metadata

The accessControl property specifies one or more security roles required to invoke an operation. Define it as an array of strings within each HTTP verb object:

"GET": {
    "accessControl": ["ROLE_USER", "ROLE_ADMIN"]
}

Role names should match the roles defined in your oeablSecurity.csv configuration file or your Spring Security configuration.

Note: In earlier versions of the operationmap.schema.json file, this property was incorrectly documented as "acl". The DataObjectHandler has always expected the property name "accessControl". If you previously used "acl" in your mapping files based on the schema definition, rename it to "accessControl" for the property to take effect.

How access control affects OpenAPI output

By default, the OpenAPI writer does not include security information in generated output. To expose access control metadata in your OpenAPI specification, you must programmatically enable it in your OpenAPI service writer configuration. This is an intentional opt-in design. Upgrading to a version that supports this feature does not change existing OpenAPI output behavior.

When security output is enabled:

  • If you configure a named security scheme (such as bearerAuth or oauth2), the writer maps accessControl roles to standard OpenAPI security requirement objects under that scheme.
  • If no security scheme is configured, the writer outputs roles using an extension property named x-required-roles on each operation.
  • Operations whose accessControl roles indicate the requesting user does not have access are omitted from the generated specification entirely. This prevents unauthorized users from discovering endpoints they cannot call.
Important: Access control in the mapping file is an application-level concern and works alongside — not instead of — server-level security. Use oeablSecurity.csv (or Spring Security configuration) as your primary line of defense to control which users can reach each API endpoint. The accessControl property in the mapping file adds a second layer, allowing developers to express finer-grained, role-based access within the application logic.

Because exposing role information in generated OpenAPI output reveals details about your security model, the OpenAPI service writer requires this to be enabled programmatically in application code rather than toggled through a configuration file. This keeps the decision to disclose security metadata within your source-controlled and reviewed deployment pipeline.

Service-level JSON Schema

When your service operations accept or return data that is not backed by an ABL Temp-Table or ProDataset, for example, a plain JSON request body or a custom response structure, you can define JSON Schema objects at the service level. The OpenAPI writer uses these definitions to generate components/schemas entries and reference them from the appropriate request or response bodies in the specification.

Defining schemas

You add a schemas object directly inside the service definition. Each named entry follows the standard JSON Schema format:

{
    "services": {
        "OrderService": {
            "version": "1.0.0",
            "schemas": {
                "OrderRequest": {
                    "type": "object",
                    "description": "Payload for creating or updating an order.",
                    "properties": {
                        "customerId": {
                            "type": "string",
                            "description": "The customer placing the order."
                        },
                        "items": {
                            "type": "array",
                            "description": "Line items in the order.",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "sku": { "type": "string" },
                                    "quantity": { "type": "integer" }
                                }
                            }
                        }
                    }
                },
                "OrderResponse": {
                    "type": "object",
                    "properties": {
                        "orderId": { "type": "string" },
                        "status": { "type": "string" }
                    }
                }
            }
        }
    }
}

Schema definitions can be nested to any depth. You can add a description to the schema itself and to individual properties. Both flow into the generated OpenAPI output.

Referencing schemas from operations

To associate a schema with a request or response body, set the ablName of the body parameter to match the schema name you defined:

"/orders": {
    "POST": {
        "contentType": "application/json",
        "statusCode": 201,
        "description": "Create a new order.",
        "entity": {
            "name": "MyApp.OrderService",
            "function": "CreateOrder",
            "arg": [
                {
                    "ablName": "OrderRequest",
                    "ablType": "CLASS Progress.Json.ObjectModel.JsonObject",
                    "ioMode": "INPUT",
                    "msgElem": { "type": "body" }
                },
                {
                    "ablName": "OrderResponse",
                    "ablType": "CLASS Progress.Json.ObjectModel.JsonObject",
                    "ioMode": "OUTPUT",
                    "msgElem": { "type": "body" }
                }
            ]
        }
    }
}

The OpenAPI writer matches the ablName of each argument with a field or body message element against the service-level schemas entries. When a match is found, the generated specification uses a $ref to the corresponding schema definition rather than an untyped object.

Note: The ablName property was chosen as the matching key so that multiple arguments can share the same schema definition. Any argument whose ablName matches an entry in schemas resolves to that schema, regardless of how many arguments reference it. Different names point to different schemas — for example, CustomerOrder (a single object) and CustomerOrders (an array of CustomerOrder items) would each map to their own definition. You can use standard JSON Schema $ref declarations within your schema definitions to express these relationships, which OpenAPI viewers resolve and render accordingly.
Note: Both body and field message element types can be matched against service-level JSON Schema definitions. However, path parameters, header parameters, and query parameters are always received as simple strings and do not use schema references.

How JSON Schema coexists with ABL schema types

The DOH supports three types of schema definitions in mapping files:

Schema type Identified by Use case
ProDataset Contains "type": "dataset" structure with nested tables ABL DataSet-backed operations
Temp-Table Contains "type": "table" structure with field definitions ABL Temp-Table-backed operations
JSON Schema Contains "type": "object" or "type": "array" at the top level Non-ABL JSON structures

The DOH inspects the structure of each schema entry to determine its type. If a definition does not match the expected format for a dataset or temp-table, it falls through to be treated as a JSON Schema and is passed through to the OpenAPI writer as-is.

Tip: ABL schemas (Temp-Table and ProDataset) are typically generated by Progress Developer Studio for OpenEdge tooling and are unlikely to require manual editing. JSON Schema definitions, however, are authored by hand since no generation tooling currently produces them. The syntax follows the standard JSON Schema format, and most AI coding assistants can produce valid definitions when given the operationmap.schema.json file as a reference. Validate your JSON Schema entries against that file before deploying to ensure they are well-formed.