Skip to content

Commit f79c65e

Browse files
authored
Merge pull request #173 from telefonicaid/issue/get-compatibility
Issue `GET` compatibility
2 parents 384a7aa + 2626666 commit f79c65e

9 files changed

Lines changed: 443 additions & 22 deletions

File tree

CHANGES_NEXT_RELEASE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add: Pentaho CDA backward compatibility now supports browser-style `GET /plugin/cda/api/doQuery` with query params, including `outputType` (`json`, `csv`, `xls`) (#108)

doc/03_api.md

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
- [Delete DA](#delete-da-delete-visibilityfdasfdaiddasdaid)
3333
- [Data operations](#data-operations)
3434
- [Data query](#data-query-get-visibilityfdasfdaiddasdaiddata)
35-
- [Query (Pentaho CDA legacy support)](#query-post-plugincdaapidoquery-pentaho-cda-legacy-support)
35+
- [Query (Pentaho CDA legacy support)](#query-plugincdaapidoquery-pentaho-cda-legacy-support)
3636
- [Navigation](#-navigation)
3737

3838
## Introduction
@@ -1733,7 +1733,7 @@ curl -i -X GET "http://localhost:8080/public/fdas/fda_alarms/das/da_all_alarms/d
17331733
-H "Accept: application/x-ndjson"
17341734
```
17351735

1736-
#### Query `POST /plugin/cda/api/doQuery` (Pentaho CDA legacy support)
1736+
#### Query `/plugin/cda/api/doQuery` (Pentaho CDA legacy support)
17371737

17381738
This endpoint provides backward compatibility with legacy Pentaho CDA clients.
17391739

@@ -1742,28 +1742,47 @@ executions and transforming the response into CDA-compatible format.
17421742

17431743
This endpoint does **not** execute queries directly. It delegates execution to the FDA query engine.
17441744

1745+
Supported methods:
1746+
1747+
- `GET /plugin/cda/api/doQuery`
1748+
- `POST /plugin/cda/api/doQuery`
1749+
17451750
_**Request headers**_
17461751

17471752
| Header | Optional | Description | Example |
17481753
| ---------------- | -------- | ------------------------------------------------------------------------ | ------------------ |
1749-
| `Content-Type` | | Must be `application/x-www-form-urlencoded` ||
1754+
| `Content-Type` | | For `POST`, should be `application/x-www-form-urlencoded` ||
17501755
| `Fiware-Service` || Tenant/service name. If not present, it is derived from the `path` field | `trantor` |
1751-
| `Accept` || Currently ignored (response is always JSON CDA format) | `application/json` |
1756+
| `Accept` || Ignored when `outputType` is provided. If omitted, defaults to JSON | `application/json` |
1757+
1758+
---
1759+
1760+
_**Request payload**_
1761+
1762+
For `POST`, send as `application/x-www-form-urlencoded` body.
1763+
1764+
For `GET`, send as query parameters.
1765+
1766+
| Field | Optional | Description | Example |
1767+
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
1768+
| `path` | | Path used to resolve context (`visibility`, `service`, and FDA id). Supported formats include `/public/<service>/...` and `home/<service>/verticals/public/<fda>.cda`. If no explicit FDA id is present, it defaults to `dataAccessId`. | `/public/service/verticals/sql/fda1` |
1769+
| `dataAccessId` | | Identifier of the Data Access (DA) inside the FDA | `da1` |
1770+
| `outputType` || Format of the returned results. **Default:** `json`. Allowed values: `json`, `csv`, `xls`. | `csv` |
1771+
| `param*` || Query parameters prefixed with `param`. If omitted, no DA query parameters are passed. | `parammunicipality=NA` |
1772+
| `pageSize` || Pagination size (must be handled explicitly by the DA). If omitted, this field is not passed and DA defaults apply. | `10` |
1773+
| `pageStart` || Pagination offset (must be handled explicitly by the DA). If omitted, this field is not passed and DA defaults apply. | `0` |
17521774

17531775
---
17541776

1755-
_**Request body (form-urlencoded)**_
1777+
_**Path-to-scope convention (legacy CDA)**_
17561778

1757-
The request body must be sent as `application/x-www-form-urlencoded`.
1779+
In this compatibility endpoint, `visibility` and `servicePath` are resolved from `path` to preserve Pentaho legacy
1780+
behavior:
17581781

1759-
| Field | Optional | Description | Example |
1760-
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
1761-
| `path` | | Path used to resolve service. FDA identifier defaults to `dataAccessId` if path only has visibility and service. | `/public/service/verticals/sql/fda1` |
1762-
| `dataAccessId` | | Identifier of the Data Access (DA) inside the FDA | `da1` |
1763-
| `outputType` || Format of the returned results. **Default:** `json`. Allowed values: `json`, `csv`, `xls`. | `csv` |
1764-
| `param*` || Query parameters prefixed with `param`. If omitted, no DA query parameters are passed. | `parammunicipality=NA` |
1765-
| `pageSize` || Pagination size (must be handled explicitly by the DA). If omitted, this field is not passed and DA defaults apply. | `10` |
1766-
| `pageStart` || Pagination offset (must be handled explicitly by the DA). If omitted, this field is not passed and DA defaults apply. | `0` |
1782+
- `visibility`: extracted from `path` (`public` or `private`)
1783+
- `servicePath`: normalized as `/${visibility}`
1784+
1785+
This convention is intentional and backward compatible with legacy clients that did not manage `Fiware-ServicePath`.
17671786

17681787
---
17691788

@@ -1813,6 +1832,19 @@ curl -i -X POST "http://localhost:8085/plugin/cda/api/doQuery" \
18131832
-d "pageSize=10"
18141833
```
18151834

1835+
_**Example Request (GET + query params, JSON default):**_
1836+
1837+
```bash
1838+
curl -i -X GET "http://localhost:8085/plugin/cda/api/doQuery?path=/public/trantor/verticals/sql/fda1&dataAccessId=da1&paramminAge=25&pageSize=10&pageStart=0"
1839+
```
1840+
1841+
_**Example Request (GET legacy CKAN/Pentaho style + CSV):**_
1842+
1843+
```bash
1844+
curl -i -X GET "http://localhost:8085/plugin/cda/api/doQuery?path=home/trantor/verticals/public/environment.cda&dataAccessId=airqualityobserved&paramstart=2023-01-01%2000%3A00%3A00&paramfinish=2023-03-30%2023%3A59%3A59&_TRUST_USER_=opendata_trantor&outputType=csv" \
1845+
--output results.csv
1846+
```
1847+
18161848
_**Example Response (JSON):**_
18171849

18181850
```json

doc/05_advanced_topics.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ FDA includes a compatibility layer to support legacy Pentaho CDA clients.
136136
### Endpoint
137137

138138
```
139+
GET /plugin/cda/api/doQuery
139140
POST /plugin/cda/api/doQuery
140141
```
141142

@@ -145,6 +146,7 @@ This layer:
145146

146147
- Translates CDA-style requests into FDA execution calls
147148
- Resolves `service` from `path` if not explicitly provided
149+
- Resolves `visibility` and `servicePath` from `path` (with `servicePath = /<visibility>`)
148150
- Resolves `fdaId` from the end of `path` field if it includes more than `service` and `visibility`, otherwise
149151
defaults to `dataAccessId`. It strips the `fdaId` at the end of the path of the `.cda` extension if present
150152
- Uses `dataAccessId` as DA identifier
@@ -202,6 +204,8 @@ Currently unsupported features:
202204

203205
These may be implemented in future versions if required.
204206

207+
Detailed reference available at: [`CDA legacy compatibility`](/doc/AdvancedTopics/cda_legacy_compatibility.md)
208+
205209
---
206210

207211
## 🧭 Navigation
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# CDA Legacy Compatibility
2+
3+
## Scope
4+
5+
This note documents how FDA keeps backward compatibility with legacy Pentaho CDA clients.
6+
7+
Compatibility endpoint:
8+
9+
- `GET /plugin/cda/api/doQuery`
10+
- `POST /plugin/cda/api/doQuery`
11+
12+
## Why this exists
13+
14+
Legacy CDA clients (for example old BI and CKAN integrations) usually send requests with:
15+
16+
- no FIWARE headers
17+
- transport-level query parameters (`param*`, `pageSize`, `pageStart`)
18+
- `path` as the source of tenant/scope context
19+
20+
FDA keeps this behavior intentionally in the compatibility endpoint to avoid forcing migrations in those clients.
21+
22+
## Scope resolution convention
23+
24+
In CDA compatibility mode, context is derived from `path`:
25+
26+
- `service`: extracted from `path`
27+
- `visibility`: extracted from `path` (`public` or `private`)
28+
- `servicePath`: normalized as `/${visibility}`
29+
30+
This mirrors historic usage where `Fiware-ServicePath` was not part of the CDA contract.
31+
32+
Supported path styles include:
33+
34+
- `/public/<service>/verticals/sql/<fdaId>`
35+
- `/private/<service>/verticals/sql/<fdaId>`
36+
- `home/<service>/verticals/public/<fdaId>.cda`
37+
- `home/<service>/verticals/private/<fdaId>.cda`
38+
39+
If `fdaId` cannot be inferred from path, it falls back to `dataAccessId`.
40+
41+
## Request mapping
42+
43+
Input fields are mapped as follows:
44+
45+
- `dataAccessId` -> DA id
46+
- `param*` -> DA params (prefix removed)
47+
- `pageSize`, `pageStart` -> forwarded as-is
48+
- `outputType` -> response format (`json`, `csv`, `xls`)
49+
50+
Unsupported compatibility feature:
51+
52+
- `param_not_*`
53+
54+
## Behavior notes
55+
56+
- For `json`, response is CDA-like: `{ metadata, resultset, queryInfo }`.
57+
- For `csv` and `xls`, FDA returns file downloads.
58+
- `_TRUST_USER_` can be present in requests and is ignored by FDA.
59+
60+
## Recommended usage
61+
62+
- Use this endpoint only for legacy clients that require CDA wire format.
63+
- Use native FDA endpoints for new integrations.

src/index.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -636,18 +636,22 @@ app.delete('/datasources/:datasourceId', async (req, res) => {
636636
return res.sendStatus(204);
637637
});
638638

639-
app.post('/plugin/cda/api/doQuery', async (req, res) => {
640-
const startTime = Date.now();
639+
function getCdaRequestParams(req) {
640+
return req.method === 'GET' ? req.query ?? {} : req.body ?? {};
641+
}
642+
643+
async function handleCdaDoQuery(req, res) {
644+
const requestParams = getCdaRequestParams(req);
645+
const { path, dataAccessId } = requestParams;
641646

642-
const { path, dataAccessId, ...rest } = req.body;
643647
if (!path || !dataAccessId) {
644648
return res.status(400).json({
645649
error: 'BadRequest',
646650
description: 'Missing params in the request',
647651
});
648652
}
649653

650-
const rawOutputType = req.body.outputType || DEFAULT_OUTPUT_TYPE;
654+
const rawOutputType = requestParams.outputType || DEFAULT_OUTPUT_TYPE;
651655

652656
if (!VALID_OUTPUT_TYPES.includes(rawOutputType)) {
653657
return res.status(400).json({
@@ -658,7 +662,7 @@ app.post('/plugin/cda/api/doQuery', async (req, res) => {
658662

659663
try {
660664
const result = await handleCdaQuery({
661-
body: req.body,
665+
body: requestParams,
662666
outputType: rawOutputType,
663667
});
664668

@@ -672,7 +676,10 @@ app.post('/plugin/cda/api/doQuery', async (req, res) => {
672676
description: err.message || 'Unexpected error executing query',
673677
});
674678
}
675-
});
679+
}
680+
681+
app.post('/plugin/cda/api/doQuery', handleCdaDoQuery);
682+
app.get('/plugin/cda/api/doQuery', handleCdaDoQuery);
676683

677684
// eslint-disable-next-line no-unused-vars
678685
app.use((err, req, res, next) => {

src/lib/compat/cdaAdapter.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import { executeQuery } from '../fda.js';
2626

27+
const VALID_VISIBILITIES = new Set(['public', 'private']);
28+
2729
export async function handleCdaQuery({ body, outputType = 'json' }) {
2830
const { service, visibility, fdaId, daId, queryParams, servicePath } =
2931
adaptCdaParams(body);
@@ -52,8 +54,7 @@ function adaptCdaParams(body) {
5254

5355
// --------- RESOLVE SERVICE ----------
5456
const pathParts = path.split('/').filter(Boolean);
55-
const visibility = pathParts[0] || 'private';
56-
const service = pathParts.length <= 1 ? pathParts[0] : pathParts[1];
57+
const { visibility, service } = resolveServiceContext(pathParts);
5758
const servicePath = `/${visibility}`;
5859

5960
// fdaId comes at the end of the path (we remove .cda extension if present)
@@ -95,6 +96,34 @@ function adaptCdaParams(body) {
9596
};
9697
}
9798

99+
function resolveServiceContext(pathParts) {
100+
// Supported styles:
101+
// - /public/<service>/...
102+
// - home/<service>/verticals/public/<file>.cda
103+
if (VALID_VISIBILITIES.has(pathParts[0])) {
104+
return {
105+
visibility: pathParts[0],
106+
service: pathParts.length <= 1 ? pathParts[0] : pathParts[1],
107+
};
108+
}
109+
110+
if (pathParts[0] === 'home' && pathParts.length > 1) {
111+
const visibilityPart = pathParts.find((part, index) => {
112+
return index > 1 && VALID_VISIBILITIES.has(part);
113+
});
114+
115+
return {
116+
visibility: visibilityPart || 'public',
117+
service: pathParts[1],
118+
};
119+
}
120+
121+
return {
122+
visibility: pathParts[0] || 'private',
123+
service: pathParts.length <= 1 ? pathParts[0] : pathParts[1],
124+
};
125+
}
126+
98127
function adaptToCdaFormat(rows, { pageStart = 0, pageSize = 0 }) {
99128
if (!rows.length) {
100129
return {

test/integration/suites/cdaCompatibility.integration.tests.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,60 @@ export function registerCdaCompatibilityIntegrationTests({
137137
expect(ndjsonAttempt.json).toHaveProperty('resultset');
138138
});
139139

140+
test('GET /plugin/cda/api/doQuery supports query params without FIWARE headers', async () => {
141+
const baseUrl = getBaseUrl();
142+
const url = new URL(`${baseUrl}/plugin/cda/api/doQuery`);
143+
144+
url.searchParams.set('path', `/public/${service}/verticals/sql/${fdaId}`);
145+
url.searchParams.set('dataAccessId', daId);
146+
url.searchParams.set('paramminAge', '25');
147+
url.searchParams.set('pageSize', '2');
148+
url.searchParams.set('pageStart', '0');
149+
150+
const res = await httpReq({
151+
method: 'GET',
152+
url: url.toString(),
153+
});
154+
155+
expect(res.status).toBe(200);
156+
expect(res.json).toHaveProperty('metadata');
157+
expect(res.json).toHaveProperty('resultset');
158+
expect(res.json).toHaveProperty('queryInfo');
159+
expect(res.json.queryInfo.pageStart).toBe(0);
160+
expect(res.json.queryInfo.pageSize).toBe(2);
161+
});
162+
163+
test('GET /plugin/cda/api/doQuery supports legacy home path and outputType query param', async () => {
164+
const baseUrl = getBaseUrl();
165+
const url = new URL(`${baseUrl}/plugin/cda/api/doQuery`);
166+
167+
url.searchParams.set(
168+
'path',
169+
`home/${service}/verticals/public/${fdaId}.cda`,
170+
);
171+
url.searchParams.set('dataAccessId', daId);
172+
url.searchParams.set('paramminAge', '25');
173+
url.searchParams.set('outputType', 'csv');
174+
url.searchParams.set('_TRUST_USER_', `opendata_${service}`);
175+
176+
const res = await httpReqRaw({
177+
method: 'GET',
178+
url: url.toString(),
179+
});
180+
181+
expect(res.status).toBe(200);
182+
expect(res.headers['content-type']).toContain('text/csv');
183+
expect(res.headers['content-disposition']).toContain('results.csv');
184+
185+
const lines = res.text
186+
.split('\n')
187+
.map((line) => line.trim())
188+
.filter(Boolean);
189+
expect(lines[0]).toBe('id,name,age');
190+
expect(lines[1]).toBe('1,ana,30');
191+
expect(lines[2]).toBe('3,carlos,40');
192+
});
193+
140194
test('POST /plugin/cda/api/doQuery rejects scope mismatch', async () => {
141195
const baseUrl = getBaseUrl();
142196
const privateFdaId = 'fda_cda_scope_private';

0 commit comments

Comments
 (0)