From 30b1e3d0a8f5588094c6dbd2b94ef48a3cfe0015 Mon Sep 17 00:00:00 2001 From: Tom Schindl Date: Sat, 26 Oct 2024 23:20:52 +0200 Subject: [PATCH] generate server DTOs --- dsl/src/cli/java-gen-utils.ts | 11 ++ dsl/src/cli/java-server-jakarta-ws/enum.ts | 36 ++++++ .../cli/java-server-jakarta-ws/generator.ts | 36 ++++++ dsl/src/cli/java-server-jakarta-ws/record.ts | 70 ++++++++++++ dsl/src/cli/java-server-jakarta-ws/union.ts | 108 ++++++++++++++++++ dsl/src/cli/main.ts | 2 + 6 files changed, 263 insertions(+) create mode 100644 dsl/src/cli/java-server-jakarta-ws/enum.ts create mode 100644 dsl/src/cli/java-server-jakarta-ws/record.ts create mode 100644 dsl/src/cli/java-server-jakarta-ws/union.ts diff --git a/dsl/src/cli/java-gen-utils.ts b/dsl/src/cli/java-gen-utils.ts index 0eda06a..2e44f4d 100644 --- a/dsl/src/cli/java-gen-utils.ts +++ b/dsl/src/cli/java-gen-utils.ts @@ -66,6 +66,11 @@ export type JavaRestClientJDKGeneratorConfig = ArtifactGenerationConfig & { nativeTypeSubstitues?: Record } +export type JavaServerJakartaWSConfig = ArtifactGenerationConfig & { + targetFolder: string + rootPackageName: string + nativeTypeSubstitues?: Record +} export function isJavaClientAPIGeneratorConfig(config: ArtifactGeneratorConfig): config is JavaClientAPIGeneratorConfig { return 'targetFolder' in config && typeof config.targetFolder === 'string' @@ -77,6 +82,12 @@ export function isJavaRestClientJDKGeneratorConfig(config: ArtifactGeneratorConf && 'rootPackageName' in config && typeof config.rootPackageName === 'string'; } +export function isJavaServerJakartaWSConfig(config: ArtifactGeneratorConfig): config is JavaServerJakartaWSConfig { + return 'targetFolder' in config && typeof config.targetFolder === 'string' + && 'rootPackageName' in config && typeof config.rootPackageName === 'string'; +} + + export function generateCompilationUnit(packageName: string, importCollector: JavaImportsCollector, content: CompositeGeneratorNode) { const node = new CompositeGeneratorNode() node.append('// Generated by RSD - Do not modify',NL) diff --git a/dsl/src/cli/java-server-jakarta-ws/enum.ts b/dsl/src/cli/java-server-jakarta-ws/enum.ts new file mode 100644 index 0000000..9fe6aba --- /dev/null +++ b/dsl/src/cli/java-server-jakarta-ws/enum.ts @@ -0,0 +1,36 @@ +import { CompositeGeneratorNode, NL, toString } from "langium/generate"; + +import { Artifact } from "../artifact-generator.js"; +import { JavaServerJakartaWSConfig, toPath } from "../java-gen-utils.js"; +import { MEnumType, MInlineEnumType } from "../model.js"; + +export function generateEnum(t: MEnumType, artifactConfig: JavaServerJakartaWSConfig): Artifact { + const packageName = `${artifactConfig.rootPackageName}.dto`; + + const node = new CompositeGeneratorNode() + node.append('// Generated by RSD - Do not modify',NL) + node.append(`package ${packageName};`, NL, NL) + node.append(`public enum ${t.name} {`, NL) + node.indent( child => { + t.entries.forEach( e => child.append(e.name, ',', NL) ) + } ) + node.append('}',NL) + + return { + name: `${t.name}.java`, + content: toString(node), + path: toPath(artifactConfig.targetFolder, packageName) + }; +} + +export function generateInlineEnum(t: MInlineEnumType, name: string, node: CompositeGeneratorNode) { + node.indent( child => { + child.append(`public enum ${name} {`, NL); + child.indent( enumBody => { + t.entries.forEach( e => { + enumBody.append(`${e.name},`, NL); + }) + } ) + child.append('}', NL, NL) + }); +} \ No newline at end of file diff --git a/dsl/src/cli/java-server-jakarta-ws/generator.ts b/dsl/src/cli/java-server-jakarta-ws/generator.ts index e69de29..8a5cd5b 100644 --- a/dsl/src/cli/java-server-jakarta-ws/generator.ts +++ b/dsl/src/cli/java-server-jakarta-ws/generator.ts @@ -0,0 +1,36 @@ +import chalk from "chalk"; +import { isMEnumType, isMRecordType, isMUnionType, MResolvedRSDModel, MResolvedUserType } from "../model.js"; +import { Artifact, ArtifactGenerationConfig, ArtifactGeneratorConfig } from "../artifact-generator.js"; +import { isJavaServerJakartaWSConfig, JavaServerJakartaWSConfig } from "../java-gen-utils.js"; +import { isDefined } from "../util.js"; +import { generateRecord } from "./record.js"; +import { generateUnion } from "./union.js"; + +export function generate(model: MResolvedRSDModel, generatorConfig: ArtifactGenerationConfig, artifactConfig: ArtifactGeneratorConfig): readonly Artifact [] { + console.log(chalk.cyan('Generating Java-Server-Jakarta-WS')); + + if( ! isJavaServerJakartaWSConfig(artifactConfig) ) { + console.log(chalk.red(' Invalid configuration passed aborted artifact generation')); + return []; + } + + const result = model.elements.map( e => generateType(e, artifactConfig) ).filter(isDefined) + + return result; +} + +function generateType(t: MResolvedUserType, artifactConfig: JavaServerJakartaWSConfig): Artifact | undefined { + if( isMEnumType(t) ) { + + } else if( isMRecordType(t) ) { + return generateRecord(t, artifactConfig); + } else if( isMUnionType(t) ) { + return generateUnion(t, artifactConfig) + } + return undefined; +} + +export default { + name: 'java-server-jakarta-ws', + generate +} \ No newline at end of file diff --git a/dsl/src/cli/java-server-jakarta-ws/record.ts b/dsl/src/cli/java-server-jakarta-ws/record.ts new file mode 100644 index 0000000..f94b482 --- /dev/null +++ b/dsl/src/cli/java-server-jakarta-ws/record.ts @@ -0,0 +1,70 @@ +import { CompositeGeneratorNode, NL, toString } from "langium/generate"; +import { Artifact } from "../artifact-generator.js"; +import { builtinToJavaType, generateCompilationUnit, JavaImportsCollector, JavaServerJakartaWSConfig, resolveObjectType, resolveType, toPath } from "../java-gen-utils.js"; +import { allRecordProperties, isMInlineEnumType, isMKeyProperty, isMProperty, isMRevisionProperty, MResolvedRecordType } from "../model.js"; +import { toFirstUpper } from "../util.js"; +import { generateInlineEnum } from "./enum.js"; + +export function generateRecord(t: MResolvedRecordType, artifactConfig: JavaServerJakartaWSConfig): Artifact | undefined { + if( t.resolved.unions.length === 1 ) { + return undefined; + } + const packageName = `${artifactConfig.rootPackageName}.dto`; + + const importCollector = new JavaImportsCollector(packageName); + const fqn = importCollector.importType.bind(importCollector); + + return { + name: `${t.name}DTO.java`, + content: toString(generateCompilationUnit(packageName, importCollector, generateRecordContent(t, artifactConfig, fqn))), + path: toPath(artifactConfig.targetFolder, packageName) + }; +} + +export function generateRecordContent(t: MResolvedRecordType, artifactConfig: JavaServerJakartaWSConfig, fqn: (type: string) => string): CompositeGeneratorNode { + const node = new CompositeGeneratorNode(); + + const allProps = allRecordProperties(t); + + node.append(`public record ${t.name}DTO(`,NL) + node.indent( param => { + allProps.forEach( (property, idx, arr) => { + const end = idx + 1 < arr.length ? ',' : ') {' + if( isMKeyProperty(property) ) { + param.append(`${builtinToJavaType(property.type, fqn)} ${property.name}`, end, NL) + } else if( isMRevisionProperty(property) ) { + param.append(`${builtinToJavaType(property.type, fqn)} ${property.name}`, end, NL) + } else { + if( property.variant === 'union' || property.variant === 'record' ) { + if( property.array ) { + param.append(`${fqn('java.util.List')}<${property.type}DTO> ${property.name}`, end, NL) + } else { + param.append(`${property.type}DTO ${property.name}`, end, NL) + } + } else if( typeof property.type === 'string' ) { + if( property.array ) { + param.append(`${fqn('java.util.List')}<${resolveObjectType(property.type, artifactConfig.nativeTypeSubstitues, fqn)}> ${property.name}`, end, NL) + } else { + param.append(`${resolveType(property.type, artifactConfig.nativeTypeSubstitues, fqn)} ${property.name}`, end, NL) + } + } else { + param.append(`${toFirstUpper(property.name)} ${property.name}`, end, NL) + } + } + }); + }) + + allProps + .filter(isMProperty) + .filter(p => p.variant === 'inline-enum') + .forEach( p => { + const inlineEnum = p.type; + if( isMInlineEnumType(inlineEnum) ) { + generateInlineEnum(inlineEnum, toFirstUpper(p.name), node) + } + }); + + node.append('}') + + return node; +} \ No newline at end of file diff --git a/dsl/src/cli/java-server-jakarta-ws/union.ts b/dsl/src/cli/java-server-jakarta-ws/union.ts new file mode 100644 index 0000000..23ea466 --- /dev/null +++ b/dsl/src/cli/java-server-jakarta-ws/union.ts @@ -0,0 +1,108 @@ +import { CompositeGeneratorNode, IndentNode, NL, toString } from "langium/generate"; +import { Artifact } from "../artifact-generator.js"; +import { builtinToJavaType, generateCompilationUnit, JavaImportsCollector, JavaServerJakartaWSConfig, resolveObjectType, resolveType, toPath } from "../java-gen-utils.js"; +import { allRecordProperties, isMInlineEnumType, isMKeyProperty, isMProperty, isMRevisionProperty, MKeyProperty, MProperty, MResolvedRecordType, MResolvedUnionType, MRevisionProperty } from "../model.js"; +import { generateInlineEnum } from "./enum.js"; +import { toFirstUpper } from "../util.js"; + +export function generateUnion(t: MResolvedUnionType, artifactConfig: JavaServerJakartaWSConfig): Artifact { + const packageName = `${artifactConfig.rootPackageName}.dto`; + + const importCollector = new JavaImportsCollector(packageName); + const fqn = importCollector.importType.bind(importCollector); + + const JsonbTypeInfo = fqn('jakarta.json.bind.annotation.JsonbTypeInfo'); + const JsonbSubtype = fqn('jakarta.json.bind.annotation.JsonbSubtype'); + + const childRecords = t.resolved.records.filter(r => r.resolved.unions.length === 1); + const node = new CompositeGeneratorNode(); + node.append(`@${JsonbTypeInfo}({`,NL) + node.indent( child => { + childRecords.forEach( r => { + const desc = (t.descriminatorAliases ?? {})[r.name] ?? r.name; + child.append(`@${JsonbSubtype}(alias = "${desc}", type = ${t.name}DTO.${r.name}DTO.class),`,NL); + } ) + } ); + + node.append('})',NL) + node.append(`public abstract class ${t.name}DTO {`,NL) + t.resolved.sharedProps + .filter(isMProperty) + .filter(p => p.variant === 'inline-enum') + .forEach( p => { + const inlineEnum = p.type; + if( isMInlineEnumType(inlineEnum) ) { + generateInlineEnum(inlineEnum, toFirstUpper(p.name), node) + } + }); + + + node.indent( child => { + t.resolved.sharedProps.forEach( p => generateProperty(child, p, artifactConfig, fqn) ) + }) + + + node.indent( child => { + childRecords.forEach( r => { + child.appendNewLine() + generateUnionRecordContent(child, r, t, artifactConfig, fqn); + } ) + }) + + + node.append('}'); + + return { + name: `${t.name}DTO.java`, + content: toString(generateCompilationUnit(packageName, importCollector, node)), + path: toPath(artifactConfig.targetFolder, packageName) + }; +} + +function generateUnionRecordContent(node: IndentNode, t: MResolvedRecordType, p: MResolvedUnionType, artifactConfig: JavaServerJakartaWSConfig, fqn: (type: string) => string) { + node.append(`public static class ${t.name}DTO extends ${p.name}DTO {`,NL) + + const sharedProps = t.resolved.unions.flatMap(u => u.resolved.sharedProps); + + const allProps = allRecordProperties(t); + allProps + .filter(isMProperty) + .filter(p => p.variant === 'inline-enum') + .filter(p => !sharedProps.includes(p)) + .forEach( p => { + const inlineEnum = p.type; + if( isMInlineEnumType(inlineEnum) ) { + generateInlineEnum(inlineEnum, toFirstUpper(p.name), node) + } + }); + + node.indent( child => { + allProps.filter(p => !sharedProps.includes(p)).forEach( p => generateProperty(child, p, artifactConfig, fqn)) + }) + + node.append('}',NL) +} + +function generateProperty(node: IndentNode, property: MKeyProperty | MRevisionProperty | MProperty, artifactConfig: JavaServerJakartaWSConfig, fqn: (type: string) => string) { + if( isMKeyProperty(property) ) { + node.append(`public ${builtinToJavaType(property.type, fqn)} ${property.name};`,NL) + } else if( isMRevisionProperty(property) ) { + node.append(`public ${builtinToJavaType(property.type, fqn)} ${property.name};`,NL) + } else { + if( property.variant === 'union' || property.variant === 'record' ) { + if( property.array ) { + node.append(`public ${fqn('java.util.List')}<${property.type}DTO> ${property.name};`,NL) + } else { + node.append(`public ${property.type}DTO ${property.name};`,NL) + } + } else if( typeof property.type === 'string' ) { + if( property.array ) { + node.append(`public ${fqn('java.util.List')}<${resolveObjectType(property.type, artifactConfig.nativeTypeSubstitues, fqn)}> ${property.name};`,NL) + } else { + node.append(`public ${resolveType(property.type, artifactConfig.nativeTypeSubstitues, fqn)} ${property.name};`,NL) + } + } else { + node.append(`public ${toFirstUpper(property.name)} ${property.name};`,NL) + } + } +} \ No newline at end of file diff --git a/dsl/src/cli/main.ts b/dsl/src/cli/main.ts index 634828d..68955a7 100644 --- a/dsl/src/cli/main.ts +++ b/dsl/src/cli/main.ts @@ -15,6 +15,7 @@ import { Artifact, ArtifactGenerator, isArtifactGenerationConfig } from './artif import JavaClientAPI from './java-client-api/generator.js'; import JavaRestClientJDK from './java-rest-client-jdk/generator.js'; +import JavaServerJakartaWS from './java-server-jakarta-ws/generator.js'; import { existsSync } from 'node:fs'; @@ -26,6 +27,7 @@ const packageContent = await fs.readFile(packagePath, 'utf-8'); const generatorRegistry = new Map(); generatorRegistry.set(JavaClientAPI.name, JavaClientAPI); generatorRegistry.set(JavaRestClientJDK.name, JavaRestClientJDK); +generatorRegistry.set(JavaServerJakartaWS.name, JavaServerJakartaWS); export const generateAction = async (fileName: string, opts: ModelGenerateOptions): Promise => { const services = createRemoteServiceDescriptionServices(NodeFileSystem);