# The runtime: BusinessBaseController & friends `xlyEntry/src/main/java/com/xly/web/businessweb/` is where the metadata-driven runtime lives. This page is the maintainer's map of the controllers and services that carry most of the generic form runtime. ## The load-bearing controllers | Class | Package | Role | Most-cited endpoints | |---|---|---|---| | `BusinessBaseController` | `web/businessweb/` | Universal CRUD for metadata-driven modules. The default API surface for every form. | `/business/getModelBysId/{moduleId}`, `/business/getBusinessDataByFormcustomId/{formId}`, `/business/addUpdateDelBusinessData`, `/business/getSelectDataBysControlId/{controlId}` | | `BusinessConfigformController` | `web/businessweb/` | Per-user / per-group display customization for an existing form, not the base form-definition CRUD. | `/configform/getConfigformData/{moduleId}`, `/configform/sHandleConfigform`, `/configform/sCopyConfigform` | | `GdsmoduleController` | `web/systemweb/` | Module-tree and module-definition CRUD used by the builder side. | `/gdsmodule/getModuleTreePro`, `/gdsmodule/addGdsmodule`, `/gdsmodule/updateGdsmodule` | | `GdsconfigformController` | `web/systemweb/` | Form-master and form-slave metadata CRUD. | endpoints under `/gdsconfigform/*` | | `GdsconfigtbController` | `web/systemweb/` | Virtual-table master/slave metadata CRUD. | endpoints under `/gdsconfigtb/*` | | `BusinessTreeGridController` | `web/businessweb/` | Tree-grid endpoints. In this branch, the proc-backed path is implemented; the plain `getTreeGrid` service method is still a stub. | `/treegrid/getTreeGridByPro/{formId}` | | `GenericProcedureCallController` | `web/businessweb/` | Generic stored-procedure invocation by name + parameters. | `/procedureCall/doGenericProcedureCall` | | `ConfigformPanelController` | `web/businessweb/` | Panel-layout persistence in `gdsconfigformpanel`. | `/panel/get/{sFormId}`, `/panel/save/{sFormId}` | | `CheckFlowController` | `web/businessweb/` | Activiti workflow surface (approve / reject / view) — only meaningful when workflow is deployed. | endpoints under `/checkFlow/*` | Note that the controllers split across **two packages**: `businessweb/` hosts the runtime-facing endpoints, while `systemweb/` hosts the metadata-CRUD endpoints used by the builder side. Both compile into the same `xlyEntry` WAR. ## The four-table read For any metadata-driven module, the request lifecycle (see [Concepts → request lifecycle](../../concepts/request-lifecycle.md)) boils down to: ```java public Map getModelBysId(Map map) { List> formList = this.getModelConfigByModleId(map); // 1. join gdsmodule⋈form-master⋈form-slave List> fList = businessGdsconfigformsService.getFormconstData(qMap); // 2. gdsformconst List> jList = businessGdsconfigformsService.getJurisdictionData(qMap); // 3. gdsjurisdiction (skipped for ADMIN) Map billnosettingMap = businessGdsconfigformsService.getBillnosettingData(param); // 4. sysbillnosettings List> reportList = printReportService.getReportData(qMap); // 5. sysreport return composite(formList, fList, jList, billnosettingMap, reportList); } ``` — `BusinessBaseServiceImpl.java`. Read this method first; the rest of the runtime is variations on it. ## The five-key composite returned | Key | Source | Frontend uses for | |---|---|---| | `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave` (+ overlays) | Form layout | | `gdsformconst` | `gdsformconst` | Form-level constants, dropdown labels | | `gdsjurisdiction` | `gdsjurisdiction` | Per-button / per-data permissions | | `billnosetting` | `sysbillnosettings` | Document numbering rules | | `report` | `sysreport` | Print templates | ## The save endpoint `POST /business/addUpdateDelBusinessData` bundles add+update+delete in one transactional batch. The frontend supplies `sTable` and a `column` map per row, in this shape: ```json { "addData": [{"sTable": "", "column": {"sId": "", ...}}], "updateData": [{"sTable": "
", "column": {"sId": "", ...}}], "delData": [{"sTable": "
", "column": {"sId": "", ...}}] } ``` When `gdsmodule.sSaveProName` is empty, the framework's default Add/Update path runs — `AddDelUpdCommonServiceImpl.java`. When it's populated, the named stored proc is invoked instead. ## Multi-tenant boundary Every controller method's first non-trivial line is: ```java RequestAddParamUtil.me().addParams(params, userInfo); ``` — `xlyPersist/.../RequestAddParamUtil.java`, 56 lines (xlyApi has its own near-identical 57-line copy at `xlyApi/.../api/util/RequestAddParamUtil.java`). This is the single point where the user's session identity (`sBrandsId`, `sSubsidiaryId`, `sBrId`, `sSuId`, `sLoginId`, `sLanguage`, `sTeamId`, `sMachineId`, plus eight more — sixteen keys total) is injected into the downstream `params` map. Every MyBatis query and stored-procedure call that references `#{sBrandsId}`/`#{sSubsidiaryId}` is automatically scoped from there. A new controller method that doesn't call `RequestAddParamUtil` is a **multi-tenant bug**. ## Security concerns to audit Two flagged in slices that belong here permanently: 1. **`sTable` validation in `addUpdateDelBusinessData`.** The frontend names the target table directly. The runtime *must* cross-check that the supplied table is one of the form's authorised backing tables, or it's a privilege-escalation surface. Confirm the check exists; if it doesn't, raise it as a security ticket. Tracked in the [Slice 1 v2 follow-up](../../slices/01-hello-world.md#open-verification-items). 2. **ADMIN bypasses permissions.** `BusinessBaseServiceImpl` skips the `gdsjurisdiction` load entirely for `UserType.ADMIN`. ADMIN account governance must come from outside the app. ## Cache invalidation When BACK saves a metadata change, a JMS message fires and `ConsumerChangeGdsModuleThread` (in `xlyErpJmsConsumer`) clears the cached metadata on every running node. See [cache invalidation on metadata change](cache-invalidation.md).