UNPKG

gen-jhipster

Version:

VHipster - Spring Boot + Angular/React/Vue in one handy generator

863 lines (862 loc) 53.1 kB
/** * Copyright 2013-2026 the original author or authors from the JHipster project. * * This file is part of the JHipster project, see https://www.jhipster.tech/ * for more information. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { passthrough } from '@yeoman/transform'; import chalk from 'chalk'; import { lowerFirst, sortedUniqBy } from 'lodash-es'; import { isFileStateModified } from 'mem-fs-editor/state'; import { APPLICATION_TYPE_MICROSERVICE } from "../../lib/core/application-types.js"; import { cacheTypes, databaseTypes, fieldTypes, searchEngineTypes, testFrameworkTypes, websocketTypes } from "../../lib/jhipster/index.js"; import { mutateData } from "../../lib/utils/index.js"; import BaseApplicationGenerator from "../base-application/index.js"; import { createNeedleCallback, isWin32 } from "../base-core/support/index.js"; import { editPropertiesFileCallback } from "../base-core/support/properties-file.js"; import { ADD_SPRING_MILESTONE_REPOSITORY } from "../generator-constants.js"; import { addJavaImport, generateKeyStore, javaBeanCase } from "../java/support/index.js"; import { getJavaValueGeneratorForType, getSpecificationBuildForType, insertContentIntoApplicationProperties, } from "../server/support/index.js"; import { mutateFilterableField, mutateFilterableRelationship } from "./application.js"; import cleanupTask from "./cleanup.js"; import { writeFiles as writeEntityFiles } from "./entity-files.js"; import { serverFiles } from "./files.js"; import { askForOptionalItems, askForServerSideOpts, askForVhipsterOpts2, askForVhipsterOpts } from "./prompts.js"; import springBootDependencies4 from "./resources/spring-boot-dependencies-4.js"; import springBootDependencies3 from "./resources/spring-boot-dependencies.js"; const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = cacheTypes; const { NO: NO_WEBSOCKET, SPRING_WEBSOCKET } = websocketTypes; const { CASSANDRA, COUCHBASE, MONGODB, NEO4J, SQL } = databaseTypes; const { ELASTICSEARCH } = searchEngineTypes; const { BYTES: TYPE_BYTES, BYTE_BUFFER: TYPE_BYTE_BUFFER } = fieldTypes.RelationalOnlyDBTypes; const { CUCUMBER, GATLING } = testFrameworkTypes; const JHIPSTER_FRAMEWORK_SB3_VERSION = '8.12.0'; export class SpringBootApplicationGenerator extends BaseApplicationGenerator { } export default class SpringBootGenerator extends SpringBootApplicationGenerator { fakeKeytool; async beforeQueue() { if (!this.fromBlueprint) { await this.composeWithBlueprints(); } if (!this.delegateToBlueprint) { await this.dependsOnBootstrap('spring-boot'); await this.dependsOnJHipster('jhipster:java'); await this.dependsOnJHipster('jhipster:java:domain'); await this.dependsOnJHipster('jhipster:java-simple-application:build-tool'); await this.dependsOnJHipster('jhipster:java:server'); } } get prompting() { return this.asPromptingTaskGroup({ askForVhipsterOpts, askForServerSideOpts, askForOptionalItems, askForVhipsterOpts2, }); } get [BaseApplicationGenerator.PROMPTING]() { return this.delegateTasksToBlueprint(() => this.prompting); } get configuring() { return this.asConfiguringTaskGroup({ syncUserWithIdpMigration({ control }) { if (this.jhipsterConfig.syncUserWithIdp === undefined && this.jhipsterConfigWithDefaults.authenticationType === 'oauth2') { if (control.isJhipsterVersionLessThan('8.1.1')) { this.jhipsterConfig.syncUserWithIdp = true; } } }, feignMigration({ control }) { const { reactive, applicationType, feignClient } = this.jhipsterConfigWithDefaults; if (feignClient) { if (reactive) { this.handleCheckFailure('Feign client is not supported by reactive applications.'); } if (applicationType !== APPLICATION_TYPE_MICROSERVICE) { this.handleCheckFailure('Feign client is only supported by microservice applications.'); } } if (feignClient === undefined && control.isJhipsterVersionLessThan('8.0.1') && !reactive && applicationType === APPLICATION_TYPE_MICROSERVICE) { this.jhipsterConfig.feignClient = true; } }, }); } get [BaseApplicationGenerator.CONFIGURING]() { return this.delegateTasksToBlueprint(() => this.configuring); } get composing() { return this.asComposingTaskGroup({ async composing() { const { applicationType, authenticationType, databaseType, enableTranslation, graalvmSupport, searchEngine, websocket, testFrameworks, feignClient, enableSwaggerCodegen, serviceDiscoveryType, } = this.jhipsterConfigWithDefaults; const { cacheProvider, messageBroker } = this.jhipsterConfigWithDefaults; await this.composeWithJHipster('jhipster:java:i18n'); await this.composeWithJHipster('docker'); await this.composeWithJHipster('jhipster:java-simple-application:jib'); await this.composeWithJHipster('jhipster:java-simple-application:code-quality'); if (enableTranslation) { await this.dependsOnBootstrap('languages'); } if (authenticationType === 'jwt' || authenticationType === 'oauth2') { await this.composeWithJHipster(`jhipster:spring-boot:${authenticationType}`); } if (graalvmSupport) { await this.composeWithJHipster('jhipster:spring-boot:graalvm'); } if (enableSwaggerCodegen) { await this.composeWithJHipster('jhipster:java-simple-application:openapi-generator'); } if (applicationType !== 'monolith' || messageBroker !== 'no' || serviceDiscoveryType !== 'no' || feignClient) { await this.composeWithJHipster('jhipster:spring-cloud'); } if (testFrameworks?.includes(CUCUMBER)) { await this.composeWithJHipster('jhipster:spring-boot:cucumber'); } if (testFrameworks?.includes(GATLING)) { await this.composeWithJHipster('jhipster:java:gatling'); } switch (databaseType) { case SQL: { await this.composeWithJHipster('jhipster:spring-boot:data-relational'); break; } case CASSANDRA: { await this.composeWithJHipster('jhipster:spring-boot:data-cassandra'); break; } case COUCHBASE: { await this.composeWithJHipster('jhipster:spring-boot:data-couchbase'); break; } case MONGODB: { await this.composeWithJHipster('jhipster:spring-boot:data-mongodb'); break; } case NEO4J: { await this.composeWithJHipster('jhipster:spring-boot:data-neo4j'); break; } } if (searchEngine === ELASTICSEARCH) { await this.composeWithJHipster('jhipster:spring-boot:data-elasticsearch'); } if (websocket === SPRING_WEBSOCKET) { await this.composeWithJHipster('jhipster:spring-boot:websocket'); } if ([EHCACHE, CAFFEINE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS].includes(cacheProvider)) { await this.composeWithJHipster('jhipster:spring-boot:cache'); } // VHipster: compose after prompts so config is available const { vauthz, enableGrpc, syncRoutesWithApisix } = this.jhipsterConfigWithDefaults; if (vauthz) { await this.composeWithJHipster('jhipster:vauthz'); } if (enableGrpc) { await this.composeWithJHipster('jhipster:spring-grpc'); } if (syncRoutesWithApisix) { await this.composeWithJHipster('jhipster:apisix'); } }, }); } get [BaseApplicationGenerator.COMPOSING]() { return this.delegateTasksToBlueprint(() => this.composing); } get composingComponent() { return this.asComposingComponentTaskGroup({ async composing() { const { clientFramework, skipClient } = this.jhipsterConfigWithDefaults; if (!skipClient && clientFramework !== 'no') { // When using prompts, clientFramework will only be known after composing priority. await this.composeWithJHipster('jhipster:java:node'); } }, async composeLanguages() { if (this.jhipsterConfigWithDefaults.enableTranslation) { await this.composeWithJHipster('languages'); } }, }); } get [BaseApplicationGenerator.COMPOSING_COMPONENT]() { return this.delegateTasksToBlueprint(() => this.composingComponent); } get preparing() { return this.asPreparingTaskGroup({ loading({ applicationDefaults }) { applicationDefaults({ communicationSpringWebsocket: ({ websocket }) => websocket === SPRING_WEBSOCKET, }); }, springBoot4({ application }) { if (!application.springBoot4) { // Latest version that supports Spring Boot 3 application.jhipsterDependenciesVersion = JHIPSTER_FRAMEWORK_SB3_VERSION; } }, springBoot3({ application }) { if (!application.springBoot4) { // Downgrade some dependencies for Spring Boot 3 Object.assign(application.javaDependencies, { 'spring-cloud-dependencies': '2025.0.0', springdoc: '2.8.15', 'neo4j-migrations-spring-boot-starter': '2.20.1', }); const prefixReplacements = { 'webmvc.test': 'test.', 'webflux.test': 'test.', webtestclient: 'test.', }; const suffixReplacements = { jackson2: 'jackson.', h2console: 'h2.', hibernate: 'orm.jpa.', mongodb: 'mongo.', restclient: 'web.client.', webflux: 'web.reactive.', 'webflux.test': 'web.reactive.', 'webmvc.test': 'web.servlet.', webtestclient: 'web.reactive.', 'health.contributor.': 'actuate.health.', 'web.server': 'web.', 'restclient.': 'web.client.', 'web.server.servlet.': 'web.servlet.server.', }; this.queueTransformStream({ name: 'reverting files to Spring Boot 3 package names', filter: file => isFileStateModified(file) && file.path.endsWith('.java') && (file.path.startsWith(this.destinationPath(application.srcMainJava)) || file.path.startsWith(this.destinationPath(application.srcTestJava))), refresh: false, }, passthrough(file => { file.contents = Buffer.from(file.contents .toString('utf8') .replace(/import org\.springframework\.boot\.(.+)\.autoconfigure\./g, (_match, p1) => `import org.springframework.boot.${prefixReplacements[p1] ?? ''}autoconfigure.${suffixReplacements[p1] ?? `${p1}.`}`) .replace(/import org\.springframework\.boot\.(restclient\.|health\.contributor\.|web\.server\.servlet\.)/g, (_match, p1) => `import org.springframework.boot.${suffixReplacements[p1] ?? p1}`) .replaceAll('import org.jspecify.annotations.Nullable;', 'import org.springframework.lang.Nullable;') .replaceAll(/import org\.testcontainers\.(mongodb)\./g, 'import org.testcontainers.containers.') .replaceAll('import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;', 'import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;')); })); this.queueTransformStream({ name: 'reverting testcontainers dependencies to v1', filter: file => isFileStateModified(file) && (file.path.endsWith('pom.xml') || file.path.endsWith('.gradle')), refresh: false, }, passthrough(file => { file.contents = Buffer.from(file.contents.toString('utf8').replaceAll('testcontainers-', '')); })); } }, updateLanguages({ application }) { if (!application.enableTranslation || !application.generateUserManagement) return; application.addLanguageCallbacks.push((_newLanguages, allLanguages) => { const { javaPackageTestDir } = application; const { ignoreNeedlesError: ignoreNonExisting } = this; this.editFile(`${javaPackageTestDir}/service/MailServiceIT.java`, { ignoreNonExisting }, createNeedleCallback({ contentToAdd: allLanguages.map(language => `"${language.languageTag}"`).join(',\n'), needle: 'jhipster-needle-i18n-language-constant', })); }); }, checksWebsocket({ application }) { const { websocket } = application; if (websocket && websocket !== NO_WEBSOCKET) { if (application.reactive) { throw new Error('Spring Websocket is not supported with reactive applications.'); } if (application.applicationType === APPLICATION_TYPE_MICROSERVICE) { throw new Error('Spring Websocket is not supported with microservice applications.'); } } }, loadSpringBootBom({ application }) { if (this.useVersionPlaceholders) { application.springBootDependencies = { 'spring-boot-dependencies': "'SPRING-BOOT-VERSION'", }; } else { const springBootDependencies = application.springBoot4 ? springBootDependencies4 : springBootDependencies3; application.springBootDependencies = this.prepareDependencies(springBootDependencies.versions, 'java'); application.javaDependencies['spring-boot'] = application.springBootDependencies['spring-boot-dependencies']; Object.assign(application.javaManagedProperties, springBootDependencies.properties); application.javaDependencies.liquibase = application.javaManagedProperties['liquibase.version']; application.javaDependencies.lombok = application.springBootDependencies.lombok ?? application.javaDependencies.lombok; application.javaDependencies.mapstruct = application.javaDependencies.mapstruct ?? application.springBootDependencies.mapstruct; } }, prepareForTemplates({ application }) { const SPRING_BOOT_VERSION = application.springBootDependencies['spring-boot-dependencies']; application.addSpringMilestoneRepository = (application.backendType ?? 'Java') === 'Java' && (ADD_SPRING_MILESTONE_REPOSITORY || SPRING_BOOT_VERSION.includes('M') || SPRING_BOOT_VERSION.includes('RC')); }, prepare({ application, applicationDefaults }) { const { reactive } = application; applicationDefaults({ __override__: false, requiresDeleteAllUsers: data => (data.anyEntityHasRelationshipWithUser && data.authenticationTypeOauth2) || data.authenticationTypeOauth2 || data.databaseTypeNeo4j || (reactive && data.databaseTypeSql) || (!reactive && data.databaseTypeMongodb) || (!reactive && data.databaseTypeCassandra), generateSpringAuditor: ctx => ctx.databaseTypeSql || ctx.databaseTypeMongodb || ctx.databaseTypeNeo4j || ctx.databaseTypeCouchbase, }); }, registerSpringFactory({ source, application }) { source.addTestSpringFactory = ({ key, value }) => { const springFactoriesFile = `${application.srcTestResources}META-INF/spring.factories`; this.editFile(springFactoriesFile, { create: true }, editPropertiesFileCallback([{ key, value, valueSep: ',' }], { sortFile: true })); }; }, addSpringIntegrationTest({ application, source }) { source.addIntegrationTestAnnotation = annotation => source.editJavaFile(this.destinationPath(`${application.javaPackageTestDir}IntegrationTest.java`), { annotations: [annotation], }); }, addApplicationYamlDocument({ application, source }) { source.addApplicationYamlDocument = content => this.editFile(this.destinationPath(`${application.srcMainResources}config/application.yml`), createNeedleCallback({ needle: 'add-application-yaml-document', autoIndent: false, contentToAdd: `---\n${content}` })); }, addLogNeedles({ source }) { source.addLogbackLogEntry = ({ file, name, level }) => this.editFile(this.destinationPath(file), createNeedleCallback({ needle: 'logback-add-log', contentToAdd: `<logger name="${name}" level="${level}"/>`, })); source.addMainLog = opts => source.addLogbackLogEntry({ file: 'src/main/resources/logback-spring.xml', ...opts }); source.addTestLog = opts => source.addLogbackLogEntry({ file: 'src/test/resources/logback.xml', ...opts }); }, addApplicationPropertiesNeedles({ application, source }) { source.addApplicationPropertiesContent = needles => this.editFile(`${application.javaPackageSrcDir}config/ApplicationProperties.java`, insertContentIntoApplicationProperties(needles)); source.addApplicationPropertiesProperty = ({ propertyName, propertyType }) => source.addApplicationPropertiesContent({ property: `private ${propertyType} ${propertyName};`, propertyGetter: ` public ${propertyType} get${javaBeanCase(propertyName)}() { return ${propertyName}; } public void set${javaBeanCase(propertyName)}(${propertyType} ${propertyName}) { this.${propertyName} = ${propertyName}; } `, }); source.addApplicationPropertiesClass = ({ propertyType, propertyName = lowerFirst(propertyType), classStructure }) => { const classProperties = Object.entries(classStructure).map(([name, type]) => ({ name, type: Array.isArray(type) ? type[0] : type, defaultVaue: Array.isArray(type) ? ` = ${type[1]}` : '', beanName: javaBeanCase(name), })); return source.addApplicationPropertiesContent({ property: `private final ${propertyType} ${propertyName} = new ${propertyType}();`, propertyGetter: ` public ${propertyType} get${javaBeanCase(propertyName)}() { return ${propertyName}; } `, propertyClass: `public static class ${propertyType} { ${classProperties.map(({ name, type, defaultVaue }) => `\n private ${type} ${name}${defaultVaue};`).join('\n')} ${classProperties.map(({ name, type, beanName }) => `\n public ${type} get${beanName}() {\n return ${name};\n }\n`).join('\n')} ${classProperties .map(({ name, type, beanName }) => `\n public void set${beanName}(${type} ${name}) {\n this.${name} = ${name};\n }`) .join('\n')} } `, }); }; }, blockhound({ application, source }) { source.addAllowBlockingCallsInside = ({ classPath, method }) => { if (!application.reactive) throw new Error('Blockhound is only supported by reactive applications'); this.editFile(`${application.javaPackageTestDir}config/JHipsterBlockHoundIntegration.java`, createNeedleCallback({ needle: 'blockhound-integration', contentToAdd: `builder.allowBlockingCallsInside("${classPath}", "${method}");`, })); }; }, addNativeHint({ source, application }) { source.addNativeHint = ({ advanced = [], declaredConstructors = [], publicConstructors = [], publicMethods = [], resources = [], }) => { this.editFile(`${application.javaPackageSrcDir}config/NativeConfiguration.java`, addJavaImport('org.springframework.aot.hint.MemberCategory'), createNeedleCallback({ contentToAdd: [ ...advanced, ...resources.map(resource => `hints.resources().registerPattern("${resource}");`), ...publicConstructors.map(classPath => `hints.reflection().registerType(${classPath}, (hint) -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));`), ...publicMethods.map(classPath => `hints.reflection().registerType(${classPath}, (hint) -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));`), ...declaredConstructors.map(classPath => `hints.reflection().registerType(${classPath}, (hint) -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));`), ], needle: 'add-native-hints', ignoreWhitespaces: true, })); }; }, needles({ source }) { const getScopeForModule = (moduleName) => { if (moduleName === 'spring-boot-properties-migrator') return 'runtime'; if (moduleName === 'spring-boot-configuration-processor') return 'annotationProcessor'; return /-test($|-|containers)/.test(moduleName) ? 'test' : undefined; }; source.addSpringBootModule = (...moduleNames) => source.addJavaDependencies?.(moduleNames .filter(module => typeof module === 'string' || module.condition) .map(module => (typeof module === 'string' ? module : module.module)) .map(name => ({ groupId: 'org.springframework.boot', artifactId: name, scope: getScopeForModule(name), }))); source.overrideProperty = props => source.addJavaProperty(props); }, }); } get [BaseApplicationGenerator.PREPARING]() { return this.delegateTasksToBlueprint(() => this.preparing); } get preparingEachEntity() { return this.asPreparingEachEntityTaskGroup({ prepareEntity({ entity, application }) { if (entity.entityRestLayer === false) { entity.entityClientModelOnly = true; } const hasAnyAuthority = (authorities) => authorities.length > 0 ? `hasAnyAuthority(${authorities.map(auth => `'${auth}'`).join(',')})` : undefined; mutateData(entity, { entityPersistenceLayer: true, entityRestLayer: true, entitySpringPreAuthorize: hasAnyAuthority(entity.entityAuthority?.split(',') ?? []), entitySpringReadPreAuthorize: hasAnyAuthority([ ...(entity.entityAuthority?.split(',') ?? []), ...(entity.entityReadAuthority?.split(',') ?? []), ]), serviceClass: ({ service }) => service === 'serviceClass', serviceImpl: ({ service }) => service === 'serviceImpl', serviceNo: ({ service }) => service === 'no', saveUserSnapshot: ({ hasRelationshipWithBuiltInUser, dto }) => application.applicationTypeMicroservice && application.authenticationTypeOauth2 && hasRelationshipWithBuiltInUser && dto === 'no', }); }, }); } get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() { return this.delegateTasksToBlueprint(() => this.preparingEachEntity); } get preparingEachEntityField() { return this.asPreparingEachEntityFieldTaskGroup({ prepareEntity({ field }) { field.fieldJavaBuildSpecification = getSpecificationBuildForType(field.fieldType); field.filterableField = ![TYPE_BYTES, TYPE_BYTE_BUFFER].includes(field.fieldType) && !field.transient; if (field.filterableField) { const { fieldType } = field; mutateData(field, mutateFilterableField); if (field.id) { field.propertyJavaFilterType = 'UUIDFilter'; field.fieldJavaBuildSpecification = getSpecificationBuildForType(fieldType); // UUID => buildSpecification } else if (field.fieldIsEnum) { const filterType = `${fieldType}Filter`; field.propertyJavaFilterType = filterType; field.propertyJavaCustomFilter = { type: filterType, superType: `Filter<${fieldType}>`, fieldType }; } else if (field.fieldTypeDuration || field.fieldTypeTemporal || field.fieldTypeCharSequence || field.fieldTypeNumeric || field.fieldTypeBoolean) { field.propertyJavaFilterType = `${fieldType}Filter`; } else if (field.fieldTypeLocalTime) { const filterType = `${fieldType}Filter`; field.propertyJavaFilterType = filterType; field.propertyJavaCustomFilter = { type: filterType, superType: `RangeFilter<${fieldType}>`, fieldType }; } else { field.propertyJavaFilterType = `Filter<${fieldType}>`; } } }, }); } get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_FIELD]() { return this.delegateTasksToBlueprint(() => this.preparingEachEntityField); } get preparingEachEntityRelationship() { return this.asPreparingEachEntityRelationshipTaskGroup({ checkUserRelationships({ entity, entityName, relationship }) { if (!entity.dtoMapstruct && relationship.otherEntity.builtInUser) { this.log.warn(`Entity ${entityName} doesn't use DTO. You should check for User data leakage through ${relationship.relationshipName} relationship.`); } }, checkDtoRelationships({ entity, entityName, relationship }) { if (entity.dto !== relationship.otherEntity.dto && !relationship.otherEntity.builtIn) { this.log.warn(`Relationship between entities with different DTO configurations can cause unexpected results. Check ${relationship.relationshipName} in the ${entityName} entity.`); } }, prepareEntity({ relationship }) { const { primaryKey } = relationship.otherEntity; if (!primaryKey) return; const otherEntityPkField = primaryKey.fields[0]; mutateData(relationship, mutateFilterableRelationship, { propertyJavaFilterType: otherEntityPkField.propertyJavaFilterType, propertyJavaFilteredType: otherEntityPkField.propertyJavaFilteredType, }); }, }); } get [BaseApplicationGenerator.PREPARING_EACH_ENTITY_RELATIONSHIP]() { return this.delegateTasksToBlueprint(() => this.preparingEachEntityRelationship); } get postPreparingEachEntity() { return this.asPostPreparingEachEntityTaskGroup({ prepareEntity({ entity }) { const { primaryKey } = entity; if (primaryKey) { primaryKey.javaBuildSpecification = getSpecificationBuildForType(primaryKey.type); primaryKey.javaValueGenerator = getJavaValueGeneratorForType(primaryKey.type); for (const field of primaryKey.fields) { field.fieldJavaValueGenerator = getJavaValueGeneratorForType(field.fieldType); } } }, prepareFilters({ application, entity }) { mutateData(entity, { entityJavaFilterableProperties: [ ...entity.fields.filter(field => field.filterableField), ...entity.relationships.filter(rel => !application.reactive || (rel.persistableRelationship && !rel.collection)), ], entityJavaCustomFilters: sortedUniqBy(entity.fields.map(field => field.propertyJavaCustomFilter).filter(Boolean), 'type'), }); mutateData(entity, { __override__: true, // Reactive with some r2dbc databases doesn't allow insertion without data. workaroundEntityCannotBeEmpty: ({ reactive, prodDatabaseType }) => reactive && ['postgresql', 'mysql', 'mariadb'].includes(prodDatabaseType), // Reactive with MariaDB doesn't allow null value at Instant fields. workaroundInstantReactiveMariaDB: ({ reactive, prodDatabaseType }) => reactive && prodDatabaseType === 'mariadb', }); }, }); } get [BaseApplicationGenerator.POST_PREPARING_EACH_ENTITY]() { return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity); } get writing() { return this.asWritingTaskGroup({ cleanupTask, resetFakeDataSeed() { this.resetEntitiesFakeData('server'); }, async writeFiles({ application }) { return this.writeFiles({ sections: serverFiles, context: application, }); }, async generateKeyStore({ application }) { const keyStoreFile = this.destinationPath(`${application.srcMainResources}config/tls/keystore.p12`); if (this.fakeKeytool) { this.writeDestination(keyStoreFile, 'fake key-tool'); } else { const result = await generateKeyStore(keyStoreFile, { packageName: application.packageName }); if (result.warning) { this.log.warn(typeof result.warning === 'string' ? result.warning : result.warning.join('\n')); this.log.warn('Writing placeholder keystore so the project can be built. Replace with a real keystore for TLS.'); this.writeDestination(keyStoreFile, 'fake key-tool'); } else { this.validateResult(result); } } }, }); } get [BaseApplicationGenerator.WRITING]() { return this.delegateTasksToBlueprint(() => this.writing); } get writingEntities() { return this.asWritingEntitiesTaskGroup({ ...writeEntityFiles(), }); } get [BaseApplicationGenerator.WRITING_ENTITIES]() { return this.delegateTasksToBlueprint(() => this.writingEntities); } get postWriting() { return this.asPostWritingTaskGroup({ baseDependencies({ application, source }) { if (application.springBoot4) { source.addSpringBootModule('spring-boot-jackson2', 'spring-boot-starter-aspectj', 'spring-boot-starter-jackson', 'spring-boot-starter-jackson-test', 'spring-boot-starter-security', 'spring-boot-starter-security-test', `spring-boot-starter-web${application.reactive ? 'flux' : 'mvc'}-test`); } else { source.addSpringBootModule('spring-boot-loader-tools', 'spring-boot-starter-aop'); if (!application.authenticationTypeOauth2) { source.addSpringBootModule('spring-boot-starter-security'); } } source.addSpringBootModule('spring-boot-configuration-processor', 'spring-boot-starter', 'spring-boot-starter-actuator', 'spring-boot-starter-mail', 'spring-boot-starter-test', 'spring-boot-starter-thymeleaf', 'spring-boot-starter-tomcat', 'spring-boot-starter-validation', `spring-boot-starter-web${application.reactive ? 'flux' : ''}`, 'spring-boot-test'); }, addJHipsterBomDependencies({ application, source }) { const { jhipsterDependenciesVersion } = application; if (application.reactive && application.graalvmSupport) { source.addNativeHint({ advanced: [ // Tomcat 'hints.reflection().registerType(org.apache.catalina.connector.RequestFacade.class, (hint) -> hint.withMembers(MemberCategory.DECLARED_FIELDS));', 'hints.reflection().registerType(org.apache.catalina.connector.ResponseFacade.class, (hint) -> hint.withMembers(MemberCategory.DECLARED_FIELDS));', ], }); } source.addJavaDefinitions?.({ dependencies: [{ groupId: 'tech.jhipster', artifactId: 'jhipster-framework', version: jhipsterDependenciesVersion }], mavenDefinition: { properties: [ { property: 'spring-boot.version', // eslint-disable-next-line no-template-curly-in-string value: '${project.parent.version}', }, ], }, }); }, addSpringdoc({ application, source }) { const springdocDependency = `springdoc-openapi-starter-${application.reactive ? 'webflux' : 'webmvc'}-api`; source.addJavaDependencies?.([ { groupId: 'org.springdoc', artifactId: springdocDependency, version: application.javaDependencies.springdoc }, ]); if (application.reactive) { source.addAllowBlockingCallsInside?.({ classPath: 'org.springdoc.core.service.OpenAPIService', method: 'build' }); source.addAllowBlockingCallsInside?.({ classPath: 'org.springdoc.core.service.OpenAPIService', method: 'getWebhooksClasses' }); source.addAllowBlockingCallsInside?.({ classPath: 'org.springdoc.core.service.AbstractRequestService', method: 'build' }); } }, addSpringSnapshotRepository({ application, source }) { if (application.buildToolMaven) { if (application.addSpringMilestoneRepository) { const springRepository = { id: 'spring-milestone', name: 'Spring Milestones', url: 'https://repo.spring.io/milestone', }; source.addMavenPluginRepository?.(springRepository); source.addMavenRepository?.(springRepository); source.addSpringBootModule?.('spring-boot-properties-migrator'); } if (application.jhipsterDependenciesVersion?.endsWith('-SNAPSHOT')) { source.addMavenRepository?.({ id: 'ossrh-snapshots', url: 'https://oss.sonatype.org/content/repositories/snapshots/', releasesEnabled: false, }); } } else if (application.buildToolGradle) { source.addGradleRepository?.({ repository: `// Local maven repository is required for libraries built locally with maven like development jhipster-bom. ${application.jhipsterDependenciesVersion?.includes('-CICD') ? '' : '// '}mavenLocal()`, }); if (application.addSpringMilestoneRepository) { source.addGradleMavenRepository?.({ url: 'https://repo.spring.io/milestone' }); } if (application.jhipsterDependenciesVersion?.endsWith('-SNAPSHOT')) { source.addGradleRepository?.({ repository: `maven { url "https://oss.sonatype.org/content/repositories/snapshots/" mavenContent { snapshotsOnly() } }`, }); } } }, addSpringBootPlugin({ application, source }) { if (application.buildToolGradle) { source.applyFromGradle({ script: 'gradle/spring-boot.gradle' }); source.addGradleDependencyCatalogPlugins?.([ { pluginName: 'gradle-git-properties', id: 'com.gorylenko.gradle-git-properties', version: application.javaDependencies['gradle-git-properties'], addToBuild: true, }, { pluginName: 'spring-boot', id: 'org.springframework.boot', version: application.javaDependencies['spring-boot'], addToBuild: true, }, ]); } }, addSpringBootCompose({ application, source }) { if (!application.dockerServices?.length) return; source.addMainLog({ name: 'org.springframework.boot.docker', level: 'WARN' }); const dockerComposeArtifact = { groupId: 'org.springframework.boot', artifactId: 'spring-boot-docker-compose' }; if (application.buildToolGradle) { source.addGradleDependency({ ...dockerComposeArtifact, scope: 'developmentOnly' }); } else if (application.buildToolMaven) { // Add dependency to profile due to jib issue https://github.com/GoogleContainerTools/jib-extensions/issues/158 source.addMavenDefinition({ profiles: [ { id: 'docker-compose', content: ` <activation> <activeByDefault>true</activeByDefault> </activation>`, }, ], }); source.addMavenDependency({ inProfile: 'docker-compose', ...dockerComposeArtifact, optional: true }); } }, sonar({ application, source }) { source.ignoreSonarRule?.({ ruleId: 'S125', ruleKey: 'xml:S125', resourceKey: `${application.srcMainResources}logback-spring.xml`, comment: `Rule https://rules.sonarsource.com/xml/RSPEC-125 is ignored, we provide commented examples`, }); if (!application.authenticationUsesCsrf && application.generateAuthenticationApi) { source.ignoreSonarRule?.({ ruleId: 'S4502', ruleKey: 'java:S4502', resourceKey: `${application.javaPackageSrcDir}config/SecurityConfiguration.java`, comment: `Rule https://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack`, }); } source.ignoreSonarRule?.({ ruleId: 'S4684', ruleKey: 'java:S4684', resourceKey: `${application.javaPackageSrcDir}web/rest/**/*`, comment: `Rule https://rules.sonarsource.com/java/RSPEC-4684`, }); source.ignoreSonarRule?.({ ruleId: 'S5145', ruleKey: 'javasecurity:S5145', resourceKey: `${application.javaPackageSrcDir}**/*`, comment: `Rule https://rules.sonarsource.com/java/RSPEC-5145 is ignored, as we use log filter to format log messages`, }); source.ignoreSonarRule?.({ ruleId: 'S6437', ruleKey: 'java:S6437', resourceKey: `${application.srcMainResources}config/application-secret-samples.yml`, comment: `Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development purposes`, }); source.ignoreSonarRule?.({ ruleId: 'S6437-2', ruleKey: 'java:S6437', resourceKey: `${application.srcMainResources}config/application-tls.yml`, comment: `Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development purposes`, }); source.ignoreSonarRule?.({ ruleId: 'S6418', ruleKey: 'yaml:S6418', resourceKey: `${application.srcMainResources}config/application-secret-samples.yml`, comment: `Rule yaml:S6418 is ignored, hardcoded passwords are provided for development and testing purposes`, }); }, dependencies({ application, source }) { source.addJavaDefinitions({ versions: [ { name: 'mapstruct', version: application.javaDependencies.mapstruct }, { name: 'archunit-junit5', version: application.javaDependencies['archunit-junit5'] }, { name: 'lombok', version: application.javaDependencies.lombok }, ], dependencies: [ { groupId: 'com.fasterxml.jackson.datatype', artifactId: 'jackson-datatype-hppc' }, { groupId: 'com.fasterxml.jackson.datatype', artifactId: 'jackson-datatype-jsr310' }, { groupId: 'io.micrometer', artifactId: 'micrometer-registry-prometheus-simpleclient' }, { groupId: 'org.apache.commons', artifactId: 'commons-lang3' }, { groupId: 'org.mapstruct', artifactId: 'mapstruct', versionRef: 'mapstruct' }, { groupId: 'org.mapstruct', artifactId: 'mapstruct-processor', versionRef: 'mapstruct', scope: 'annotationProcessor' }, { groupId: 'org.projectlombok', artifactId: 'lombok', versionRef: 'lombok', scope: 'provided' }, { groupId: 'org.projectlombok', artifactId: 'lombok', versionRef: 'lombok', scope: 'annotationProcessor' }, { groupId: 'org.projectlombok', artifactId: 'lombok-mapstruct-binding', version: '0.2.0', scope: 'annotationProcessor' }, { groupId: 'org.springframework.security', artifactId: 'spring-security-test', scope: 'test' }, { scope: 'test', groupId: 'com.tngtech.archunit', artifactId: 'archunit-junit5-api', versionRef: 'archunit-junit5', exclusions: [{ groupId: 'org.slf4j', artifactId: 'slf4j-api' }], }, { scope: 'testRuntimeOnly', groupId: 'com.tngtech.archunit', artifactId: 'archunit-junit5-engine', versionRef: 'archunit-junit5', exclusions: [{ groupId: 'org.slf4j', artifactId: 'slf4j-api' }], }, ], }, { condition: application.reactive, versions: [ { name: 'blockhound-junit-platform', version: application.javaDependencies['blockhound-junit-platform'] }, { name: 'micrometer-context-propagation', version: application.javaDependencies['micrometer-context-propagation'] }, ], dependencies: [ { groupId: 'io.netty', artifactId: 'netty-tcnative-boringssl-static', scope: 'runtime' }, { groupId: 'io.micrometer', artifactId: 'context-propagation', versionRef: 'micrometer-context-propagation' }, { groupId: 'io.projectreactor.tools', artifactId: 'blockhound-junit-platform', versionRef: 'blockhound-junit-platform', scope: 'test', }, { groupId: 'org.springframework.data', artifactId: 'spring-data-commons' }, ], }, { condition: application.addSpringMilestoneRepository, dependencies: [{ groupId: 'org.springframework.boot', artifactId: 'spring-boot-properties-migrator', scope: 'runtime' }], }); if (application.buildToolGradle && application.reactive) { this.editFile('build.gradle', { needle: 'gradle-dependency', contentToAdd: `OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem(); Architecture arch = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentArchitecture(); if (os.isMacOsX() && !arch.isAmd64()) { implementation("io.netty:netty-resolver-dns-native-macos") { artifact { classifier = "osx-aarch_64" } } }`, }); } else if (application.buildToolMaven) { source.addJavaDefinitions({ condition: application.reactive, dependencies: [{ groupId: 'io.netty', artifactId: 'netty-resolver-dns-native-macos', classifier: 'osx-aarch_64' }], }); } // Carbone/Excel dependencies for import/export source.addJavaDefinitions({ versions: [ { name: 'poi', version: '5.2.5' }, { name: 'okhttp', version: '4.12.0' }, ], dependencies: [ { groupId: 'com.squareup.okhttp3', artifactId: 'okhttp