@@ -75,16 +75,20 @@ def failsafeSection = """\
7575 </build>
7676"""
7777
78- /* * Builds a child-module pom.xml string (2-space indent). */
78+ /* * Builds a child-module pom.xml string (2-space indent).
79+ * A dependency entry is either a String (an internal module artifactId, inheriting this project's
80+ * groupId and BOM-managed version) or a Map [groupId:…, artifactId:…] for an external dependency. */
7981def modulePom = { String gId, String parentAId, String ver,
80- String aId, String desc, List< String > deps = [], String buildSection = ' ' ->
82+ String aId, String desc, List deps = [], String buildSection = ' ' ->
8183 def depsSection = ' '
8284 if (deps) {
8385 def lines = deps. collect { dep ->
86+ def dGroup = (dep instanceof Map ) ? dep. groupId : gId
87+ def dArtifact = (dep instanceof Map ) ? dep. artifactId : dep
8488 """ \
8589 <dependency>
86- <groupId>${ gId } </groupId>
87- <artifactId>${ dep } </artifactId>
90+ <groupId>${ dGroup } </groupId>
91+ <artifactId>${ dArtifact } </artifactId>
8892 </dependency>"""
8993 }. join(' \n ' )
9094 depsSection = " \n <dependencies>\n ${ lines} \n </dependencies>\n "
@@ -124,6 +128,7 @@ def pkgPath = pkg.replace('.', '/')
124128def integrationsInput = (props. getProperty(' integrations' ) ?: ' ' ). trim()
125129def serviceAreasInput = (props. getProperty(' serviceAreas' ) ?: ' ' ). trim()
126130def presentationTypesInput = (props. getProperty(' presentationTypes' ) ?: ' rest' ). trim()
131+ def includeSpring = ((props. getProperty(' includeSpring' ) ?: ' true' ). trim(). toLowerCase() != ' false' )
127132
128133// ─── Parse Integrations ───────────────────────────────────────────────────────
129134// Input format: "type:name[,type:name,...]" e.g. "database:users,rest:orders"
@@ -185,6 +190,38 @@ def projectDir = new File(request.outputDirectory)
185190def subDir = new File (projectDir, artifactId)
186191if (subDir. isDirectory()) projectDir = subDir
187192
193+ // ─── Spring configuration class generator ─────────────────────────────────────
194+ // When includeSpring is true, every module the app composes carries a @Configuration
195+ // class in its `.config` sub-package (the app's Application class @Imports them all)
196+ // and declares spring-context. Everything here is skipped when includeSpring is false.
197+
198+ def SPRING_CONTEXT = [groupId : ' org.springframework' , artifactId : ' spring-context' ]
199+ def springCtx = includeSpring ? [SPRING_CONTEXT ] : [] // appended to module deps only when Spring is enabled
200+ def configFqns = []
201+
202+ /* * Writes a @Configuration class into {module}/…/{mainSub}/config and records its FQN; no-op when Spring is off. */
203+ def configClass = { String module , String mainSub ->
204+ if (! includeSpring) return null
205+ def className = module. split(' -' ). collect { it. capitalize() }. join(' ' ) + ' Configuration'
206+ def pkgFq = (pkgPath + ' /' + mainSub + ' /config' ). replace(' /' , ' .' )
207+ def dir = " ${ module} /src/main/java/${ pkgPath} /${ mainSub} /config"
208+ writeFile(projectDir, " ${ dir} /${ className} .java" , """ \
209+ package ${ pkgFq} ;
210+
211+ import org.springframework.context.annotation.Configuration;
212+
213+ /**
214+ * Spring configuration for the ${ module} module.
215+ */
216+ @Configuration
217+ public class ${ className} {
218+ }
219+ """ )
220+ def fqn = " ${ pkgFq} .${ className} " . toString()
221+ configFqns << fqn
222+ return fqn
223+ }
224+
188225// ─── parent/pom.xml ───────────────────────────────────────────────────────────
189226// Modules listed with "../" relative path; child modules use relativePath back here.
190227// The placeholder pom.xml at the project root (generated by the archetype engine)
@@ -201,6 +238,16 @@ def dmXml = allModules.collect { mod ->
201238 </dependency>"""
202239}. join(' \n ' )
203240
241+ def parentInherit = includeSpring ? """ \
242+ <parent>
243+ <groupId>org.springframework.boot</groupId>
244+ <artifactId>spring-boot-starter-parent</artifactId>
245+ <version>4.0.6</version>
246+ <relativePath/>
247+ </parent>
248+
249+ """ : ' '
250+
204251def parentPom = """ \
205252<?xml version="1.0" encoding="UTF-8"?>
206253<project xmlns="http://maven.apache.org/POM/4.0.0"
@@ -209,7 +256,7 @@ def parentPom = """\
209256 https://maven.apache.org/xsd/maven-4.0.0.xsd">
210257 <modelVersion>4.0.0</modelVersion>
211258
212- <groupId>${ groupId} </groupId>
259+ ${ parentInherit } <groupId>${ groupId} </groupId>
213260 <artifactId>${ artifactId} </artifactId>
214261 <version>${ version} </version>
215262 <packaging>pom</packaging>
@@ -400,10 +447,12 @@ try {
400447writeFile(projectDir, ' common-domain/pom.xml' ,
401448 modulePom(groupId, artifactId, version,
402449 ' common-domain' ,
403- ' Domain classes common to all modules.' ))
450+ ' Domain classes common to all modules.' ,
451+ springCtx))
404452srcTree(projectDir, ' common-domain' , pkgPath,
405453 [' common/domain' ], [' common/domain' ],
406454 ' Domain classes common to all modules.' )
455+ configClass(' common-domain' , ' common/domain' )
407456
408457// ─── common-testing ───────────────────────────────────────────────────────────
409458
@@ -429,20 +478,22 @@ integrations.each { intg ->
429478 modulePom(groupId, artifactId, version,
430479 domMod,
431480 " Domain classes for the ${ intg.name} ${ disp} data source." ,
432- [' common-domain' ]))
481+ [' common-domain' ] + springCtx ))
433482 srcTree(projectDir, domMod, pkgPath,
434483 [domSub], [domSub],
435484 " Domain classes for the ${ intg.name} ${ disp} integration." )
485+ configClass(domMod, domSub)
436486
437487 writeFile(projectDir, " ${ implMod} /pom.xml" ,
438488 modulePom(groupId, artifactId, version,
439489 implMod,
440490 " Integration classes (DAOs, repositories) for the ${ intg.name} ${ disp} data source." ,
441- [' common-domain' , domMod],
491+ [' common-domain' , domMod] + springCtx ,
442492 failsafeSection))
443493 srcTree(projectDir, implMod, pkgPath,
444494 [implSub], [implSub],
445495 " Integration classes for the ${ intg.name} ${ disp} data source." )
496+ configClass(implMod, implSub)
446497}
447498
448499// ─── Service modules ──────────────────────────────────────────────────────────
@@ -463,15 +514,17 @@ serviceModules.each { svcMod ->
463514 writeFile(projectDir, " ${ domMod} /pom.xml" ,
464515 modulePom(groupId, artifactId, version,
465516 domMod, domDesc,
466- [' common-domain' ]))
517+ [' common-domain' ] + springCtx ))
467518 srcTree(projectDir, domMod, pkgPath, [domPkg], [domPkg], domDesc)
519+ configClass(domMod, domPkg)
468520
469521 // Business logic, depending on its own service-area domain module.
470522 writeFile(projectDir, " ${ svcMod} /pom.xml" ,
471523 modulePom(groupId, artifactId, version,
472524 svcMod, svcDesc,
473- [' common-domain' , domMod]))
525+ [' common-domain' , domMod] + springCtx ))
474526 srcTree(projectDir, svcMod, pkgPath, [svcPkg], [svcPkg], svcDesc)
527+ configClass(svcMod, svcPkg)
475528}
476529
477530// ─── Presentation: domain-{type} and presentation-{type} ──────────────────────
@@ -485,31 +538,89 @@ presentations.each { pType ->
485538 modulePom(groupId, artifactId, version,
486539 domMod,
487540 " Domain classes for the ${ disp} presentation tier." ,
488- [' common-domain' ]))
541+ [' common-domain' ] + springCtx ))
489542 srcTree(projectDir, domMod, pkgPath,
490543 [" domain/${ pType} " ], [" domain/${ pType} " ],
491544 " Domain classes for the ${ disp} presentation tier." )
545+ configClass(domMod, " domain/${ pType} " )
492546
493547 writeFile(projectDir, " ${ implMod} /pom.xml" ,
494548 modulePom(groupId, artifactId, version,
495549 implMod,
496550 " Request-handling classes (controllers) for the ${ disp} presentation tier." ,
497- [' common-domain' , domMod]))
551+ [' common-domain' , domMod] + springCtx ))
498552 srcTree(projectDir, implMod, pkgPath,
499553 [" presentation/${ pType} " ], [" presentation/${ pType} " ],
500554 " Request-handling classes for the ${ disp} presentation tier." )
555+ configClass(implMod, " presentation/${ pType} " )
501556}
502557
503558// ─── app ─────────────────────────────────────────────────────────────────────
504559
560+ def appStarters = includeSpring ? [
561+ [groupId : ' org.springframework.boot' , artifactId : ' spring-boot-starter-web' ],
562+ [groupId : ' org.springframework.boot' , artifactId : ' spring-boot-starter-data-jpa' ],
563+ ] : []
505564writeFile(projectDir, ' app/pom.xml' ,
506565 modulePom(groupId, artifactId, version,
507566 ' app' ,
508567 ' Application assembly — produces the runnable artifact.' ,
509- appDeps))
568+ appDeps + appStarters ))
510569srcTree(projectDir, ' app' , pkgPath, [' app' ], [' app' ],
511570 ' Application assembly and entry point.' )
512571
572+ if (includeSpring) {
573+ // Spring Boot entry-point classes live in the app's .config package and @Import every
574+ // module's @Configuration class so the whole tree is assembled into one context.
575+ def appConfigPkg = (pkgPath + ' /app/config' ). replace(' /' , ' .' )
576+ def importsBlock = configFqns. sort(). collect { " ${ it} .class," }. join(' \n ' )
577+ writeFile(projectDir, " app/src/main/java/${ pkgPath} /app/config/Application.java" , """ \
578+ package ${ appConfigPkg} ;
579+
580+ import org.springframework.beans.factory.annotation.Value;
581+ import org.springframework.boot.SpringApplication;
582+ import org.springframework.boot.autoconfigure.SpringBootApplication;
583+ import org.springframework.context.annotation.Import;
584+ import org.springframework.transaction.annotation.EnableTransactionManagement;
585+
586+ /**
587+ * Spring Boot main application class for running standalone in Boot configured embedded container. Not used with WAR
588+ * deployment.
589+ */
590+ @SpringBootApplication
591+ @EnableTransactionManagement
592+ @Import({
593+ ${ importsBlock}
594+ })
595+ public class Application {
596+ public static void main(final String[] args) {
597+ SpringApplication.run(Application.class, args);
598+ }
599+ }
600+ """ )
601+ writeFile(projectDir, " app/src/main/java/${ pkgPath} /app/config/AppServletInitializer.java" , """ \
602+ package ${ appConfigPkg} ;
603+
604+ import org.slf4j.Logger;
605+ import org.slf4j.LoggerFactory;
606+ import org.springframework.boot.builder.SpringApplicationBuilder;
607+ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
608+
609+ /**
610+ * web.xml configuration replacement for WAR deployment.
611+ */
612+ public class AppServletInitializer extends SpringBootServletInitializer {
613+ private final Logger log = LoggerFactory.getLogger(getClass());
614+
615+ @Override
616+ protected SpringApplicationBuilder configure(final SpringApplicationBuilder builder) {
617+ log.info("Starting application");
618+ return builder.sources(Application.class);
619+ }
620+ }
621+ """ )
622+ }
623+
513624// ─── acceptance-tests ─────────────────────────────────────────────────────────
514625
515626writeFile(projectDir, ' acceptance-tests/pom.xml' ,
0 commit comments