# The runtime: BusinessBaseController & friends `xlyEntry/src/main/java/com/xly/web/businessweb/` is where the metadata-driven runtime lives — 51 controllers, of which a few do all the heavy lifting. This page is the maintainer's map of those classes. ## The load-bearing controllers | Class | Role | Most-cited endpoints | |---|---|---| | `BusinessBaseController` | 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` | The BACK builder's API for editing form definitions. | endpoints under `/business/configform/*` | | `BusinessModelCenterController` | The BACK module-tree manager. | endpoints around `gdsmodule` CRUD | | `BusinessTreeGridController` | Tree-grid widget for hierarchical forms. | renders `sParentId`-linked records as a tree | | `BusinessParameterController` | Reads / writes `gdsparameter` (system-wide named operations). | parameter lookup and edit | | `FilterTreeController` | Saved-filter tree — the user's named search snippets in `syssearch`. | filter CRUD | | `GenericProcedureCallController` | Generic stored-procedure invocation by name + parameters. | dispatch arbitrary procs | | `CharController` | Chart configuration runtime — reads `gdsconfigchar*`. | chart fetch | | `ConfigformPanelController` | Multi-panel form config editor. | layout edit | | `BillController` | Document-bill helpers (bill numbers, status). | bill numbering, billing helpers | The remaining 41 are more specific (calculation, work-order helpers, mobile, AI, comparator-tree, etc.). ## 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:181-211`. 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` — handler at `BusinessBaseController.java:165-177` — 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); ``` — `RequestAddParamUtil.java`, 44 lines. This is the single point where the user's session identity (`sBrandsId`, `sSubsidiaryId`, `sLoginId`, `sLanguage`, `sTeamId`, `sMachineId`, …) 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.java:196-198` 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).