gen-jhipster
Version:
Spring Boot + Angular/React/Vue in one handy generator
386 lines (385 loc) • 22.1 kB
JavaScript
/**
* Copyright 2013-2024 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 { existsSync } from 'fs';
import { GENERATOR_COMMON, GENERATOR_SPRING_BOOT } from '../generator-list.js';
import BaseApplicationGenerator from '../base-application/index.js';
import { CLIENT_WEBPACK_DIR, JAVA_COMPATIBLE_VERSIONS, JAVA_VERSION, JHIPSTER_DEPENDENCIES_VERSION, LOGIN_REGEX, MAIN_DIR, SERVER_MAIN_RES_DIR, SERVER_MAIN_SRC_DIR, SERVER_TEST_RES_DIR, SERVER_TEST_SRC_DIR, TEST_DIR, } from '../generator-constants.js';
import { applicationTypes, clientFrameworkTypes, databaseTypes, entityOptions, fieldTypes, reservedKeywords, searchEngineTypes, validations, } from '../../lib/jhipster/index.js';
import { stringifyApplicationData } from '../base-application/support/index.js';
import { mutateData } from '../base/support/index.js';
import { isReservedPaginationWords } from '../../lib/jhipster/reserved-keywords.js';
import { loadStoredAppOptions } from '../app/support/index.js';
import { isReservedH2Keyword } from '../spring-data-relational/support/h2-reserved-keywords.js';
import { hibernateSnakeCase } from './support/index.js';
const { SUPPORTED_VALIDATION_RULES } = validations;
const { isReservedTableName } = reservedKeywords;
const { ANGULAR, REACT, VUE } = clientFrameworkTypes;
const { SQL, NO: NO_DATABASE } = databaseTypes;
const { GATEWAY } = applicationTypes;
const { NO: NO_SEARCH_ENGINE } = searchEngineTypes;
const { CommonDBTypes, RelationalOnlyDBTypes } = fieldTypes;
const { INSTANT } = CommonDBTypes;
const { BYTE_BUFFER } = RelationalOnlyDBTypes;
const { PaginationTypes, ServiceTypes } = entityOptions;
const { Validations: { MAX, MIN, MAXLENGTH, MINLENGTH, MAXBYTES, MINBYTES, PATTERN }, } = validations;
const { NO: NO_PAGINATION } = PaginationTypes;
const { NO: NO_SERVICE } = ServiceTypes;
export default class JHipsterServerGenerator extends BaseApplicationGenerator {
/** @type {string} */
jhipsterDependenciesVersion;
/** @type {string} */
projectVersion;
async beforeQueue() {
if (!this.fromBlueprint) {
loadStoredAppOptions.call(this);
await this.composeWithBlueprints();
}
if (!this.delegateToBlueprint) {
await this.dependsOnBootstrapApplication();
await this.dependsOnJHipster(GENERATOR_COMMON);
}
}
get composing() {
return this.asComposingTaskGroup({
async composeBackendType() {
if (!this.jhipsterConfig.backendType || ['spring-boot', 'java'].includes(this.jhipsterConfig.backendType.toLowerCase())) {
await this.composeWithJHipster(GENERATOR_SPRING_BOOT);
}
},
});
}
get [BaseApplicationGenerator.COMPOSING]() {
return this.delegateTasksToBlueprint(() => this.composing);
}
get loading() {
return this.asLoadingTaskGroup({
setupServerconsts({ application, applicationDefaults }) {
// Make constants available in templates
applicationDefaults({
MAIN_DIR,
TEST_DIR,
LOGIN_REGEX,
CLIENT_WEBPACK_DIR,
SERVER_MAIN_SRC_DIR,
SERVER_MAIN_RES_DIR,
SERVER_TEST_SRC_DIR,
SERVER_TEST_RES_DIR,
JAVA_VERSION: this.useVersionPlaceholders ? 'JAVA_VERSION' : JAVA_VERSION,
JAVA_COMPATIBLE_VERSIONS,
javaCompatibleVersions: JAVA_COMPATIBLE_VERSIONS,
ANGULAR,
VUE,
REACT,
});
if (this.projectVersion) {
application.projectVersion = this.projectVersion;
this.log.info(`Using projectVersion: ${application.projectVersion}`);
}
else {
application.projectVersion = '0.0.1-SNAPSHOT';
}
if (this.useVersionPlaceholders) {
application.jhipsterDependenciesVersion = 'JHIPSTER_DEPENDENCIES_VERSION';
}
else if (this.jhipsterDependenciesVersion) {
application.jhipsterDependenciesVersion = this.jhipsterDependenciesVersion;
this.log.info(`Using jhipsterDependenciesVersion: ${application.jhipsterDependenciesVersion}`);
}
else {
application.jhipsterDependenciesVersion = JHIPSTER_DEPENDENCIES_VERSION;
}
},
});
}
get [BaseApplicationGenerator.LOADING]() {
return this.delegateTasksToBlueprint(() => this.loading);
}
get configuringEachEntity() {
return this.asConfiguringEachEntityTaskGroup({
configureMicroservice({ application, entityConfig }) {
if (application.applicationTypeMicroservice) {
if (entityConfig.microserviceName === undefined) {
entityConfig.microserviceName = application.baseName;
}
if (entityConfig.clientRootFolder === undefined) {
entityConfig.clientRootFolder = entityConfig.microserviceName;
}
if (entityConfig.databaseType === undefined) {
entityConfig.databaseType = application.databaseType;
}
}
},
configureGateway({ application, entityConfig }) {
if (application.applicationTypeGateway) {
if (entityConfig.databaseType === undefined) {
entityConfig.databaseType = application.databaseType;
}
if (entityConfig.clientRootFolder === undefined) {
entityConfig.clientRootFolder = entityConfig.clientRootFolder = entityConfig.skipUiGrouping
? ''
: entityConfig.microserviceName;
}
}
},
configureEntitySearchEngine({ application, entityConfig }) {
const { applicationTypeMicroservice, applicationTypeGateway, clientFrameworkAny } = application;
if (entityConfig.microserviceName && !(applicationTypeMicroservice && clientFrameworkAny)) {
if (!entityConfig.searchEngine) {
// If a non-microfrontend microservice entity, should be disabled by default.
entityConfig.searchEngine = NO_SEARCH_ENGINE;
}
}
if (
// Don't touch the configuration for microservice entities published at gateways
!(applicationTypeGateway && entityConfig.microserviceName) &&
!application.searchEngineAny &&
entityConfig.searchEngine !== NO_SEARCH_ENGINE) {
if (entityConfig.searchEngine) {
this.log.warn('Search engine is enabled at entity level, but disabled at application level. Search engine will be disabled');
}
// Search engine can only be enabled at entity level and disabled at application level for gateways publishing a microservice entity
entityConfig.searchEngine = NO_SEARCH_ENGINE;
}
},
configureModelFiltering({ application, entityConfig }) {
const { databaseTypeSql, applicationTypeGateway } = application;
if (
// Don't touch the configuration for microservice entities published at gateways
!(applicationTypeGateway && entityConfig.microserviceName) &&
entityConfig.jpaMetamodelFiltering &&
(!databaseTypeSql || entityConfig.service === NO_SERVICE)) {
this.log.warn('Not compatible with jpaMetamodelFiltering, disabling');
entityConfig.jpaMetamodelFiltering = false;
}
},
configurePagination({ application, entityName, entityConfig }) {
const entityDatabaseType = entityConfig.databaseType ?? application.databaseType;
// disable pagination if there is no database, unless it’s a microservice entity published by a gateway
if (entityDatabaseType === NO_DATABASE && (application.applicationType !== GATEWAY || !entityConfig.microserviceName)) {
const errorMessage = `Pagination is not supported for entity ${entityName} when the app doesn't use a database.`;
if (!this.skipChecks) {
throw new Error(errorMessage);
}
this.log.warn(errorMessage);
entityConfig.pagination = 'no';
}
},
configureEntityTable({ application, entityName, entityConfig }) {
if ((application.applicationTypeGateway && entityConfig.microserviceName) || entityConfig.skipServer)
return;
const { jhiTablePrefix, devDatabaseTypeH2Any } = application;
const databaseType = entityConfig.prodDatabaseType ?? application.prodDatabaseType ?? entityConfig.databaseType ?? application.databaseType;
const entityTableName = entityConfig.entityTableName ?? hibernateSnakeCase(entityName);
const fixedEntityTableName = (isReservedTableName(entityTableName, databaseType) || (devDatabaseTypeH2Any && isReservedH2Keyword(entityTableName))) &&
jhiTablePrefix
? `${jhiTablePrefix}_${entityTableName}`
: entityTableName;
if (fixedEntityTableName !== entityTableName) {
entityConfig.entityTableName = fixedEntityTableName;
}
if (entityConfig.incrementalChangelog === undefined) {
// Keep entity's original incrementalChangelog option.
entityConfig.incrementalChangelog =
application.incrementalChangelog &&
!existsSync(this.destinationPath(`src/main/resources/config/liquibase/changelog/${entityConfig.annotations?.changelogDate}_added_entity_${entityConfig.name}.xml`));
}
},
configureFields({ application, entityConfig, entityName }) {
// Validate entity json field content
const fields = entityConfig.fields;
fields.forEach(field => {
// Migration from JodaTime to Java Time
if (field.fieldType === 'DateTime' || field.fieldType === 'Date') {
field.fieldType = INSTANT;
}
this._validateField(entityName, field);
if (entityConfig.pagination && entityConfig.pagination !== NO_PAGINATION && isReservedPaginationWords(field.fieldName)) {
throw new Error(`Field name '${field.fieldName}' found in ${entityConfig.name} is a reserved keyword, as it is used by Spring for pagination in the URL.`);
}
// Field type check should be ignored for entities of others microservices.
if (!field.fieldValues && (!entityConfig.microserviceName || entityConfig.microserviceName === application.baseName)) {
if (!Object.values(CommonDBTypes).includes(field.fieldType) &&
(application.databaseType !== SQL || !Object.values(RelationalOnlyDBTypes).includes(field.fieldType))) {
throw new Error(`The type '${field.fieldType}' is an unknown field type for field '${field.fieldName}' of entity '${entityConfig.name}' using '${application.databaseType}' database.`);
}
}
});
entityConfig.fields = fields;
},
configureRelationships({ entityConfig, entityName }) {
// Validate entity json relationship content
const relationships = entityConfig.relationships;
relationships.forEach(relationship => {
this._validateRelationship(entityName, relationship);
if (relationship.relationshipName === undefined) {
relationship.relationshipName = relationship.otherEntityName;
this.log.warn(`relationshipName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}, using ${relationship.otherEntityName} as fallback`);
}
// @ts-ignore deprecated property
if (relationship.useJPADerivedIdentifier) {
this.log.verboseInfo('Option useJPADerivedIdentifier is deprecated, use id instead');
relationship.options ??= {};
relationship.options.id = true;
}
});
entityConfig.relationships = relationships;
},
});
}
get [BaseApplicationGenerator.CONFIGURING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.configuringEachEntity);
}
get loadingEntities() {
return this.asLoadingEntitiesTaskGroup({
loadEntityConfig({ entitiesToLoad }) {
for (const { entityName, entityBootstrap } of entitiesToLoad) {
for (const field of entityBootstrap.fields) {
if (field.fieldType === BYTE_BUFFER) {
this.log.warn(`Cannot use validation in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
field.fieldValidate = false;
field.fieldValidateRules = [];
}
}
}
},
});
}
get [BaseApplicationGenerator.LOADING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.loadingEntities);
}
get preparingEachEntity() {
return this.asPreparingEachEntityTaskGroup({
prepareEntity({ entity }) {
mutateData(entity, {
entityPersistenceLayer: true,
entityRestLayer: true,
});
},
});
}
get [BaseApplicationGenerator.PREPARING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.preparingEachEntity);
}
get postPreparingEachEntity() {
return this.asPostPreparingEachEntityTaskGroup({
checkForTableName({ application, entity }) {
const databaseType = entity.prodDatabaseType ?? application.prodDatabaseType ?? entity.databaseType ?? application.databaseType;
const validation = this._validateTableName(entity.entityTableName, databaseType, entity);
if (validation !== true) {
throw new Error(validation);
}
},
});
}
get [BaseApplicationGenerator.POST_PREPARING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.postPreparingEachEntity);
}
/** @inheritdoc */
get default() {
return this.asDefaultTaskGroup({
checkCompositeIds({ entities }) {
const entitiesWithCompositeIds = entities.filter(entity => entity.primaryKey?.composite);
if (entitiesWithCompositeIds.length > 0) {
throw new Error(`Composite id is not supported. Defined in ${entitiesWithCompositeIds.map(entity => `${entity.name} (${entity.primaryKey.fields.map(field => field.fieldName)})`)}`);
}
},
});
}
get [BaseApplicationGenerator.DEFAULT]() {
return this.delegateTasksToBlueprint(() => this.default);
}
/**
* Validate the entityTableName
* @return {true|string} true for a valid value or error message.
*/
_validateTableName(entityTableName, prodDatabaseType, entity) {
const jhiTablePrefix = entity.jhiTablePrefix;
const instructions = `You can specify a different table name in your JDL file or change it in .jhipster/${entity.name}.json file and then run again 'jhipster entity ${entity.name}.'`;
if (!/^([a-zA-Z0-9_]*)$/.test(entityTableName)) {
return `The table name cannot contain special characters.
${instructions}`;
}
if (!entityTableName) {
return 'The table name cannot be empty';
}
if (isReservedTableName(entityTableName, prodDatabaseType)) {
if (jhiTablePrefix) {
this.log.warn(`The table name cannot contain the '${entityTableName.toUpperCase()}' reserved keyword, so it will be prefixed with '${jhiTablePrefix}_'.
${instructions}`);
entity.entityTableName = `${jhiTablePrefix}_${entityTableName}`;
}
else {
this.log.warn(`The table name contain the '${entityTableName.toUpperCase()}' reserved keyword but you have defined an empty jhiPrefix so it won't be prefixed and thus the generated application might not work'.
${instructions}`);
}
}
return true;
}
_validateField(entityName, field) {
if (field.fieldName === undefined) {
throw new Error(`fieldName is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldType === undefined) {
throw new Error(`fieldType is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules !== undefined) {
if (!Array.isArray(field.fieldValidateRules)) {
throw new Error(`fieldValidateRules is not an array in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
field.fieldValidateRules.forEach(fieldValidateRule => {
if (!SUPPORTED_VALIDATION_RULES.includes(fieldValidateRule)) {
throw new Error(`fieldValidateRules contains unknown validation rule ${fieldValidateRule} in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)} [supported validation rules ${SUPPORTED_VALIDATION_RULES}]`);
}
});
if (field.fieldValidateRules.includes(MAX) && field.fieldValidateRulesMax === undefined) {
throw new Error(`fieldValidateRulesMax is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(MIN) && field.fieldValidateRulesMin === undefined) {
throw new Error(`fieldValidateRulesMin is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(MAXLENGTH) && field.fieldValidateRulesMaxlength === undefined) {
throw new Error(`fieldValidateRulesMaxlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(MINLENGTH) && field.fieldValidateRulesMinlength === undefined) {
throw new Error(`fieldValidateRulesMinlength is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(MAXBYTES) && field.fieldValidateRulesMaxbytes === undefined) {
throw new Error(`fieldValidateRulesMaxbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(MINBYTES) && field.fieldValidateRulesMinbytes === undefined) {
throw new Error(`fieldValidateRulesMinbytes is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
if (field.fieldValidateRules.includes(PATTERN) && field.fieldValidateRulesPattern === undefined) {
throw new Error(`fieldValidateRulesPattern is missing in .jhipster/${entityName}.json for field ${stringifyApplicationData(field)}`);
}
}
}
_validateRelationship(entityName, relationship) {
if (relationship.otherEntityName === undefined) {
throw new Error(`otherEntityName is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`);
}
if (relationship.relationshipType === undefined) {
throw new Error(`relationshipType is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`);
}
if (relationship.relationshipSide === undefined &&
(relationship.relationshipType === 'one-to-one' || relationship.relationshipType === 'many-to-many')) {
throw new Error(`relationshipSide is missing in .jhipster/${entityName}.json for relationship ${stringifyApplicationData(relationship)}`);
}
}
}