diff --git a/README.md b/README.md index 5049efda0..10779e0cf 100644 --- a/README.md +++ b/README.md @@ -155,3 +155,4 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU Affero General Public License along with this program. If not, see http://www.gnu.org/licenses/. SPDX-License-Identifier: AGPL-3.0-only + diff --git a/__pycache__/oneuser.cpython-310.pyc b/__pycache__/oneuser.cpython-310.pyc new file mode 100644 index 000000000..6ea0cb26d Binary files /dev/null and b/__pycache__/oneuser.cpython-310.pyc differ diff --git a/adapter/package-lock.json b/adapter/package-lock.json index 344149ac3..2fd4f14ec 100644 --- a/adapter/package-lock.json +++ b/adapter/package-lock.json @@ -13,7 +13,8 @@ "@jvalue/node-dry-pg": "1.2.1", "body-parser": "^1.19.0", "cors": "^2.8.5", - "express": "^4.17.1" + "express": "^4.17.1", + "jackson-js": "^1.1.0" }, "devDependencies": { "@jvalue/eslint-config-jvalue": "^1.1.0", @@ -4625,6 +4626,17 @@ "node": ">=8" } }, + "node_modules/jackson-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jackson-js/-/jackson-js-1.1.0.tgz", + "integrity": "sha512-EDKwt9U6dd/zNW/7p3VejSYUYgUkZ5c7v4gGAO7KhA1qLp6DXPpBjIx9ELI9LzQjpFtoL8g6CAQAhBuDmzkYMw==", + "dependencies": { + "lodash.clone": "^4.5.0", + "lodash.clonedeep": "^4.5.0", + "meriyah": "^1.9.12", + "reflect-metadata": "^0.1.13" + } + }, "node_modules/jest": { "version": "27.0.5", "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.5.tgz", @@ -6890,6 +6902,16 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6981,6 +7003,14 @@ "node": ">= 8" } }, + "node_modules/meriyah": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-1.9.15.tgz", + "integrity": "sha512-D4rT6XIYGqZab0EI/xbihUpwh0WbNRVQ35l2J/5QC2atxaI8h3KvA55DEJLBB/FRdaji7JwkNehfCRjCyjCjqw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7939,6 +7969,11 @@ "string_decoder": "~0.10.x" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -12729,6 +12764,17 @@ "istanbul-lib-report": "^3.0.0" } }, + "jackson-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jackson-js/-/jackson-js-1.1.0.tgz", + "integrity": "sha512-EDKwt9U6dd/zNW/7p3VejSYUYgUkZ5c7v4gGAO7KhA1qLp6DXPpBjIx9ELI9LzQjpFtoL8g6CAQAhBuDmzkYMw==", + "requires": { + "lodash.clone": "^4.5.0", + "lodash.clonedeep": "^4.5.0", + "meriyah": "^1.9.12", + "reflect-metadata": "^0.1.13" + } + }, "jest": { "version": "27.0.5", "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.5.tgz", @@ -14413,6 +14459,16 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -14488,6 +14544,11 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "meriyah": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-1.9.15.tgz", + "integrity": "sha512-D4rT6XIYGqZab0EI/xbihUpwh0WbNRVQ35l2J/5QC2atxaI8h3KvA55DEJLBB/FRdaji7JwkNehfCRjCyjCjqw==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -15193,6 +15254,11 @@ "string_decoder": "~0.10.x" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", diff --git a/adapter/src/api/rest/adapterEndpoint.ts b/adapter/src/api/rest/adapterEndpoint.ts new file mode 100644 index 000000000..95ce7c1c0 --- /dev/null +++ b/adapter/src/api/rest/adapterEndpoint.ts @@ -0,0 +1,109 @@ +import express from 'express'; +import { AdapterConfigValidator } from '../../model/AdapterConfig'; + + +import { asyncHandler } from './utils'; +import {ProtocolConfigValidator} from "../../model/ProtocolConfig"; +import { Format } from '../../model/enum/Format'; + +const adapterService = require( "../../services/AdapterService" ); +const APP_VERSION = "0.0.1" +export class AdapterEndpoint { + constructor() {} + + registerRoutes = (app: express.Application): void => { + app.post('/preview', asyncHandler(this.handleExecuteDataImport)); + app.post('/preview/raw', asyncHandler(this.handleExecuteRawPreview)); + app.get('/formats', asyncHandler(this.handleGetFormat)); + app.get('/protocols', asyncHandler(this.handleGetProtocols)); + app.get('/version', asyncHandler(this.handleGetApplicationVersion)); + }; + + // Adapter Endpoint + /** + * @RestController + @AllArgsConstructor + public class AdapterEndpoint { + private final Adapter adapter; + + @PostMapping(Mappings.IMPORT_PATH) + public DataImportResponse executeDataImport(@Valid @RequestBody AdapterConfig config) + throws ImporterParameterException, InterpreterParameterException, IOException { + return adapter.executeJob(config); + } + + @PostMapping(Mappings.RAW_IMPORT_PATH) + public DataImportResponse executeRawPreview(@Valid @RequestBody ProtocolConfig config) + throws ImporterParameterException { + return adapter.executeRawImport(config); + } + } + */ + + handleExecuteDataImport = async ( + req: express.Request, + res: express.Response, + ): Promise => { + const validator = new AdapterConfigValidator(); + if (!validator.validate(req.body)) { + res.status(400).json({ errors: validator.getErrors() }); + return; + } + + let adapterConfig = req.params.config + let returnDataImportResponse = adapterService.executeJob(adapterConfig); + res.status(200).send(returnDataImportResponse); + }; + + handleExecuteRawPreview = async ( + req: express.Request, + res: express.Response, + ): Promise => { + const validator = new ProtocolConfigValidator(); + if (!validator.validate(req.body)) { + res.status(400).json({ errors: validator.getErrors() }); + return; + } + let protocolConfig = req.params.config + let returnDataImportResponse = adapterService.executeRawImport(protocolConfig); + res.status(200).send(returnDataImportResponse); + }; + + /* + returns Collection of Importer + } */ + + handleGetFormat = async ( + req: express.Request, + res: express.Response, + ): Promise => { + try { + let interpreters = adapterService.getAllFormats(); + res.status(200).json(interpreters); + } catch (e) { + res.status(500).send('Error finding formats'); + } + }; + + /* + returns Collection of Importer + */ + handleGetProtocols = async ( + req: express.Request, + res: express.Response, + ): Promise => { + try { + let protocols = adapterService.getAllProtocols(); + res.status(200).json(protocols); + } catch (e) { + res.status(500).send('Error finding protocols'); + } + }; + + handleGetApplicationVersion = async ( + req: express.Request, + res: express.Response, + ): Promise => { + res.status(200).send(APP_VERSION); + }; +}; diff --git a/adapter/src/api/rest/utils.ts b/adapter/src/api/rest/utils.ts new file mode 100644 index 000000000..8e793a472 --- /dev/null +++ b/adapter/src/api/rest/utils.ts @@ -0,0 +1,19 @@ +import type { NextFunction, Request, RequestHandler, Response } from 'express'; + +/** + * A wrapper for promise returning request handlers that passes the value of a + * rejected promise to express's `next` function. + * @param handler request handler to wrap + */ +export function asyncHandler( + handler: ( + req: Request, + res: Response, + next?: NextFunction, + ) => void | Promise, +): RequestHandler { + return (req: Request, res: Response, next: NextFunction): void => { + const handlerResult = handler(req, res, next); + Promise.resolve(handlerResult).catch(next); + }; +} \ No newline at end of file diff --git a/adapter/src/importer/HttpImporter.ts b/adapter/src/importer/HttpImporter.ts new file mode 100644 index 000000000..ea98e43bc --- /dev/null +++ b/adapter/src/importer/HttpImporter.ts @@ -0,0 +1,63 @@ +import { Importer } from "./Importer"; +import { ImporterParameterDescription } from "./ImporterParameterDescription"; + +export class HttpImporter extends Importer { + getAvailableParameters(): ImporterParameterDescription[] { + throw new Error("Method not implemented."); + } + doFetch(parameters: Record): string { + throw new Error("Method not implemented."); + } + /** + * + * private final List parameters = List.of( + new ImporterParameterDescription("location", "String of the URI for the HTTP call", String.class), + new ImporterParameterDescription("encoding", + "Encoding of the source. Available encodings: ISO-8859-1, US-ASCII, UTF-8", String.class), + new ImporterParameterDescription("defaultParameters", "Default values for open parameters in the URI", false, + RuntimeParameters.class)); + private final RestTemplate restTemplate; + + @Override + public String getType() { + return "HTTP"; + } + + @Override + public String getDescription() { + return "Plain HTTP"; + } + + @Override + protected void validateParameters(Map inputParameters) throws ImporterParameterException { + super.validateParameters(inputParameters); + + String encoding = (String) inputParameters.get("encoding"); + if (!encoding.equals(StandardCharsets.ISO_8859_1.name()) && !encoding.equals(StandardCharsets.US_ASCII.name()) && !encoding.equals(StandardCharsets.UTF_8.name())) { + throw new IllegalArgumentException(getType() + " interpreter requires parameter encoding to have value " + + StandardCharsets.ISO_8859_1 + ", " + + StandardCharsets.US_ASCII + ", " + + StandardCharsets.UTF_8 + + ". Your given value " + encoding + " is invalid!"); + } + } + + @Override + public List getAvailableParameters() { + return parameters; + } + + @Override + protected String doFetch(Map parameters) throws ImporterParameterException { + String location = parameters.get("location").toString(); + try { + URI uri = URI.create(location); + byte[] rawResponse = restTemplate.getForEntity(uri, byte[].class).getBody(); + return new String(rawResponse, Charset.forName((String) parameters.get("encoding"))); + } catch (IllegalArgumentException e) { + throw new ImporterParameterException(e.getMessage()); + } + } + */ + +} \ No newline at end of file diff --git a/adapter/src/importer/Importer.ts b/adapter/src/importer/Importer.ts new file mode 100644 index 000000000..66a7a33b6 --- /dev/null +++ b/adapter/src/importer/Importer.ts @@ -0,0 +1,78 @@ +import { ImporterParameterDescription } from "./ImporterParameterDescription"; + +export abstract class Importer { + type: string | undefined; + description: string | undefined; + parameters: Record | undefined; + + getRequiredParameters(): Array { + return [] + } + + abstract getAvailableParameters() :Array; + + fetch(parameters:Record ): string { //throws ImporterParameterException + this.validateParameters(parameters); + return this.doFetch(parameters); + } + + abstract doFetch(parameters: Record): string; //throws ImporterParameterException + + validateParameters(inputParameters: Record) { //throws ImporterParameterException; + + } +/** + * + * public abstract String getType(); + + public abstract String getDescription(); + + @JsonProperty("parameters") + public abstract List getAvailableParameters(); + + protected List getRequiredParameters() { + return getAvailableParameters().stream() + .filter(ImporterParameterDescription::isRequired).collect(Collectors.toList()); + } + + public final String fetch(Map parameters) throws ImporterParameterException { + validateParameters(parameters); + return doFetch(parameters); + } + + protected abstract String doFetch(Map parameters) throws ImporterParameterException; + + protected void validateParameters(Map inputParameters) throws ImporterParameterException { + boolean illegalArguments = false; + String illegalArgumentsMessage = ""; + + + List possibleParameters = getAvailableParameters().stream() + .map(ImporterParameterDescription::getName).collect(Collectors.toList()); + var unnecessaryArguments = inputParameters.keySet().stream() + .filter(o -> !possibleParameters.contains(o)).collect(Collectors.toList()); + if (unnecessaryArguments.size() > 0) { + illegalArguments = true; + for (var argument : + unnecessaryArguments) { + illegalArgumentsMessage += argument + " is not needed by importer \n"; + } + } + + for (ImporterParameterDescription requiredParameter : getRequiredParameters()) { + if (inputParameters.get(requiredParameter.getName()) == null) { + illegalArguments = true; + illegalArgumentsMessage = illegalArgumentsMessage + getType() + " importer requires parameter " + + requiredParameter.getName() + "/n"; + + } else if (inputParameters.get(requiredParameter.getName()).getClass() != requiredParameter.getType()) { + illegalArguments = true; + illegalArgumentsMessage = illegalArgumentsMessage + getType() + " importer requires parameter " + + requiredParameter.getName() + " to be type " + requiredParameter.getType().toString() + "/n"; + } + } + if (illegalArguments) { + throw new ImporterParameterException(illegalArgumentsMessage); + } + */ +} \ No newline at end of file diff --git a/adapter/src/importer/ImporterParameterDescription.ts b/adapter/src/importer/ImporterParameterDescription.ts new file mode 100644 index 000000000..a845c1f6c --- /dev/null +++ b/adapter/src/importer/ImporterParameterDescription.ts @@ -0,0 +1,12 @@ +export class ImporterParameterDescription { + name: string; + description: string; + required: boolean | undefined; + type: unknown; + + constructor (name: string, description: string, type: unknown) { + this.name = name; + this.description = description; + this.type = type; + } +} \ No newline at end of file diff --git a/adapter/src/index.ts b/adapter/src/index.ts index 4290c9331..6686f4c6d 100644 --- a/adapter/src/index.ts +++ b/adapter/src/index.ts @@ -3,6 +3,7 @@ import { Server } from 'http'; import bodyParser from 'body-parser'; import cors from 'cors'; import express from 'express'; +import { AdapterEndpoint } from './api/rest/adapterEndpoint'; export const port = 8080; const API_VERSION = '0.0.1'; @@ -20,10 +21,8 @@ async function main(): Promise { res.status(200).send('I am alive!'); }); - app.get('/version', (req: express.Request, res: express.Response): void => { - res.header('Content-Type', 'text/plain'); - res.status(200).send(API_VERSION); - }); + const adapterEndpoint = new AdapterEndpoint(); + adapterEndpoint.registerRoutes(app); server = app.listen(port, () => { console.log(`Listening on port ${port}`); diff --git a/adapter/src/interpreter/CsvInterpreter.ts b/adapter/src/interpreter/CsvInterpreter.ts new file mode 100644 index 000000000..3af13c6db --- /dev/null +++ b/adapter/src/interpreter/CsvInterpreter.ts @@ -0,0 +1,102 @@ +import {Interpreter} from "./Interpreter"; + + +export class CsvInterpreter extends Interpreter { + doInterpret(data: string, parameters: Record): string { + throw new Error("Method not implemented."); + } + + /* + private final List parameters = List.of( + new InterpreterParameterDescription("columnSeparator", "Column delimiter character, only one character supported", String.class), + new InterpreterParameterDescription("lineSeparator", "Line delimiter character, only \\r, \\r\\n, and \\n supported", String.class), + new InterpreterParameterDescription("skipFirstDataRow", "Skip first data row (after header)", Boolean.class), + new InterpreterParameterDescription("firstRowAsHeader", "Interpret first row as header for columns", Boolean.class) + ); + private final CsvMapper mapper = new CsvMapper().enable(CsvParser.Feature.WRAP_AS_ARRAY); + private final ObjectMapper jsonMapper = new ObjectMapper(); + + @Override + public String getType() { + return "CSV"; + } + + @Override + public String getDescription() { + return "Interpret data as CSV data"; + } + + @Override + public List getAvailableParameters() { + return parameters; + } + + @Override + protected void validateParameters(Map inputParameters) throws InterpreterParameterException { + super.validateParameters(inputParameters); + + String lineSeparator = (String) inputParameters.get("lineSeparator"); + if (!lineSeparator.equals("\n") && !lineSeparator.equals("\r") && !lineSeparator.equals("\r\n")) { + throw new InterpreterParameterException(getType() + " interpreter requires parameter lineSeparator to have" + + " value \\n, \\r, or \\r\\n. Your given value " + lineSeparator + " is invalid!"); + } + + String columnSeparator = (String) inputParameters.get("columnSeparator"); + if (columnSeparator.length() != 1) { + throw new InterpreterParameterException(getType() + " interpreter requires parameter columnSeparator to have" + + " length 1. Your given value " + columnSeparator + " is invalid!"); + } + } + + @Override + protected JsonNode doInterpret(String data, Map parameters) throws IOException { + CsvSchema csvSchema = createSchema(parameters); + if ((boolean) parameters.get("firstRowAsHeader")) { + return parseWithHeader(data, csvSchema); + } else { + return parseWithoutHeader(data, csvSchema); + } + } + + private CsvSchema createSchema(Map parameters) { + CsvSchema csvSchema = CsvSchema + .emptySchema() + .withColumnSeparator(((String) parameters.get("columnSeparator")).charAt(0)) + .withLineSeparator((String) parameters.get("lineSeparator")) + .withSkipFirstDataRow((boolean) parameters.get("skipFirstDataRow")); + if ((boolean) parameters.get("firstRowAsHeader")) { + csvSchema = csvSchema + .withHeader(); + } + return csvSchema; + } + + private JsonNode parseWithoutHeader(String data, CsvSchema csvSchema) throws IOException { + MappingIterator allLines = mapper + .readerFor(Object[].class) + .with(csvSchema) + .readValues(data); + + ArrayNode result = mapper.createArrayNode(); + while (allLines.hasNext()) { + result.add(jsonMapper.valueToTree(allLines.next())); + } + + return result; + } + + private JsonNode parseWithHeader(String data, CsvSchema csvSchema) throws IOException { + MappingIterator> allLines = mapper + .readerFor(Map.class) + .with(csvSchema) + .readValues(data); + + ArrayNode result = mapper.createArrayNode(); + while (allLines.hasNext()) { + result.add(jsonMapper.valueToTree(allLines.next())); + } + + return result; + } + */ +} diff --git a/adapter/src/interpreter/Interpreter.ts b/adapter/src/interpreter/Interpreter.ts new file mode 100644 index 000000000..6d44997b7 --- /dev/null +++ b/adapter/src/interpreter/Interpreter.ts @@ -0,0 +1,34 @@ +export abstract class Interpreter { + type: string | undefined; + description: string | undefined; + parameters: Record | undefined; + + + interpret(data: string, parameters: Record): string { //throws IOException + this.validateParameters(parameters); + return this.doInterpret(data, parameters); + } + + abstract doInterpret(data: string, parameters: Record): string //throws IOException; + + validateParameters(inputParameters: Record) { //throws InterpreterParameterException + /*boolean illegalArguments = false; + String illegalArgumentsMessage = ""; + + for (InterpreterParameterDescription requiredParameter : getAvailableParameters()) { + if (inputParameters.get(requiredParameter.getName()) == null) { + illegalArguments = true; + illegalArgumentsMessage = illegalArgumentsMessage + getType() + " interpreter requires parameter " + + requiredParameter.getName() + "/n"; + + } else if (inputParameters.get(requiredParameter.getName()).getClass() != requiredParameter.getType()) { + illegalArguments = true; + illegalArgumentsMessage = illegalArgumentsMessage + getType() + " interpreter requires parameter " + + requiredParameter.getName() + " to be type " + requiredParameter.getType().toString() + "/n"; + } + } + if (illegalArguments) { + throw new InterpreterParameterException(illegalArgumentsMessage); + }*/ + } + } diff --git a/adapter/src/interpreter/JsonInterpreter.ts b/adapter/src/interpreter/JsonInterpreter.ts new file mode 100644 index 000000000..d4e3604ee --- /dev/null +++ b/adapter/src/interpreter/JsonInterpreter.ts @@ -0,0 +1,33 @@ +import {Interpreter} from "./Interpreter"; + +export class JsonInterpreter extends Interpreter { + doInterpret(data: string, parameters: Record): string { + throw new Error("Method not implemented."); + } + + /* + private final List parameters = List.of(); + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public String getType() { + return "JSON"; + } + + @Override + public String getDescription() { + return "Interpret data as JSON data"; + } + + @Override + public List getAvailableParameters() { + return parameters; + } + + @Override + protected JsonNode doInterpret(String data, Map parameters) throws IOException { + return mapper.readTree(data); + } +} + */ +} diff --git a/adapter/src/interpreter/XmlInterpreter.ts b/adapter/src/interpreter/XmlInterpreter.ts new file mode 100644 index 000000000..e871ff965 --- /dev/null +++ b/adapter/src/interpreter/XmlInterpreter.ts @@ -0,0 +1,38 @@ +import {Interpreter} from "./Interpreter"; + +export class XmlInterpreter extends Interpreter{ + doInterpret(data: string, parameters: Record): string { + throw new Error("Method not implemented."); + } + + /* + private final List parameters = List.of(); + private final XmlMapper mapper = new XmlMapper(); + + public XmlInterpreter() { + mapper.registerModule(new SimpleModule().addDeserializer(Object.class, new UntypedXMLArrayDeserializer())); + } + + @Override + public String getType() { + return "XML"; + } + + @Override + public String getDescription() { + return "Interpret data as XML data"; + } + + @Override + public List getAvailableParameters() { + return parameters; + + } + + @Override + public JsonNode doInterpret(String data, Map parameters) throws IOException { + Object result = mapper.readValue(data, Object.class); + return mapper.valueToTree(result); + } + */ +} diff --git a/adapter/src/model/AdapterConfig.ts b/adapter/src/model/AdapterConfig.ts new file mode 100644 index 000000000..e6b6266b3 --- /dev/null +++ b/adapter/src/model/AdapterConfig.ts @@ -0,0 +1,35 @@ +import { FormatConfig } from "./FormatConfig"; +import { ProtocolConfig } from "./ProtocolConfig"; +import { validators } from '@jvalue/node-dry-basics'; + +export interface AdapterConfig { + protocolConfig: ProtocolConfig; + formatConfig: FormatConfig; +} + +export class AdapterConfigValidator { + private errors: string[] = []; + + validate(request: unknown): request is AdapterConfig { + this.errors = []; + if (!validators.isObject(request)) { + this.errors.push("'AdapterConfig' must be an object"); + return false; + } + if (!validators.hasProperty(request, 'protocol')) { + this.errors.push("'protocol' property is missing"); + } else if (!validators.isObject(request.protocol)) { + this.errors.push("'protocol' must be a string"); + } + if (!validators.hasProperty(request, 'format')) { + this.errors.push("'format' property is missing"); + } else if (!validators.isObject(request.format)) { + this.errors.push("'format' must be an object or array"); + } + return this.errors.length === 0; + } + + getErrors(): string[] { + return this.errors; + } +} \ No newline at end of file diff --git a/adapter/src/model/DataImportResponse.ts b/adapter/src/model/DataImportResponse.ts new file mode 100644 index 000000000..d865fa06e --- /dev/null +++ b/adapter/src/model/DataImportResponse.ts @@ -0,0 +1,7 @@ +export class DataImportResponse { + data: string; + + constructor(data: string) { + this.data = data; + } +} diff --git a/adapter/src/model/FormatConfig.ts b/adapter/src/model/FormatConfig.ts new file mode 100644 index 000000000..4e8d8a7ee --- /dev/null +++ b/adapter/src/model/FormatConfig.ts @@ -0,0 +1,10 @@ +import { Format } from "./enum/Format"; + +export class FormatConfig { + format:Format; + parameters: Record | undefined; + constructor(format: Format, parameters: Record) { + this.format = format; + this.parameters = parameters; + } + } diff --git a/adapter/src/model/ProtocolConfig.ts b/adapter/src/model/ProtocolConfig.ts new file mode 100644 index 000000000..c8b7fbbfd --- /dev/null +++ b/adapter/src/model/ProtocolConfig.ts @@ -0,0 +1,34 @@ +import { Protocol } from "./enum/Protocol"; +import { validators } from '@jvalue/node-dry-basics'; + +export interface ProtocolConfig { + protocol:Protocol; + parameters: Map | undefined; +} + +export class ProtocolConfigValidator { + private errors: string[] = []; + + validate(request: unknown): request is ProtocolConfig { + this.errors = []; + if (!validators.isObject(request)) { + this.errors.push("'ProtocolConfig' must be an object"); + return false; + } + if (!validators.hasProperty(request, 'type')) { + this.errors.push("'type' property is missing"); + } else if (!validators.isObject(request.type)) { + this.errors.push("'type' must be a string"); + } + if (!validators.hasProperty(request, 'parameters')) { + this.errors.push("'parameters' property is missing"); + } else if (!validators.isObject(request.parameters)) { + this.errors.push("'parameters' must be an object or array"); + } + return this.errors.length === 0; + } + + getErrors(): string[] { + return this.errors; + } +} diff --git a/adapter/src/model/enum/Format.ts b/adapter/src/model/enum/Format.ts new file mode 100644 index 000000000..264fe2a91 --- /dev/null +++ b/adapter/src/model/enum/Format.ts @@ -0,0 +1,17 @@ +import { CsvInterpreter } from "../../interpreter/CsvInterpreter"; +import { JsonInterpreter } from "../../interpreter/JsonInterpreter"; +import { XmlInterpreter } from "../../interpreter/XmlInterpreter"; + +export class Format { + static readonly JSON = new JsonInterpreter(); + static readonly XML = new XmlInterpreter(); + static readonly CSV = new CsvInterpreter(); + + // private to disallow creating other instances of this type + private constructor(private readonly key: string, public readonly value: any) { + } + + toString() { + return this.key; + } +} \ No newline at end of file diff --git a/adapter/src/model/enum/Protocol.ts b/adapter/src/model/enum/Protocol.ts new file mode 100644 index 000000000..5de7b34b7 --- /dev/null +++ b/adapter/src/model/enum/Protocol.ts @@ -0,0 +1,13 @@ +import { HttpImporter } from "../../importer/HttpImporter"; + +export class Protocol { + static readonly HTTP = new HttpImporter(); + + private constructor(private readonly key: string, public readonly value: any) { + } + + toString() { + return this.key; + } + +} \ No newline at end of file diff --git a/adapter/src/services/adapterService.ts b/adapter/src/services/adapterService.ts new file mode 100644 index 000000000..005d7d8c0 --- /dev/null +++ b/adapter/src/services/adapterService.ts @@ -0,0 +1,38 @@ +import { Interpreter } from "../interpreter/Interpreter"; +import { AdapterConfig } from "../model/AdapterConfig"; +import { DataImportResponse } from "../model/DataImportResponse"; +import { Format } from "../model/enum/Format"; +import { Protocol } from "../model/enum/Protocol"; + +import { ProtocolConfig } from "../model/ProtocolConfig"; + +export class AdapterService { + /** + * @description Create an instance of AdapterService + */ + constructor () { + } + + // To Implement + static getAllFormats(): Array { + return [Format.CSV, Format.JSON, Format.XML] + } + + + static getAllProtocols(): Array { + return [Protocol.HTTP] + } + + static executeJob(_adapterConfig: AdapterConfig): DataImportResponse { + // TODO IMPLEMENT + return new DataImportResponse("Data_test") + } + + static executeRawJob(_protocolConfig: ProtocolConfig): DataImportResponse { + // TODO IMPLEMENT + return new DataImportResponse("Data_test") + } + +} + + module.exports = AdapterService; diff --git a/package-lock.json b/package-lock.json index 48e341a09..987d6128b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,6 @@ { - "lockfileVersion": 1 + "name": "ods", + "lockfileVersion": 2, + "requires": true, + "packages": {} }