gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
863 lines (862 loc) • 53.1 kB
JavaScript
/**
* 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