UNPKG

gen-jhipster

Version:

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

160 lines (159 loc) 6.89 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 * as _ from 'lodash-es'; import { asWritingEntitiesTask } from "../base-application/support/task-type-inference.js"; import { SERVER_MAIN_PROTO_DIR, SERVER_MAIN_SRC_DIR } from "../generator-constants.js"; import { moveToJavaPackageSrcDir, moveToSrcMainProtoDir } from "../java/support/index.js"; import { GRPC_ENTITY_SUPPORT_FILE_DEFS } from "./files.js"; const defaultEntities = ['User', 'Authority']; const protoFiles = [ { condition: (ctx) => ctx.entityClass !== 'User', path: `${SERVER_MAIN_PROTO_DIR}`, renameTo: moveToSrcMainProtoDir, templates: ['entity/_entityUnderscoredName_.proto'], }, { condition: (ctx) => ctx.entityClass === 'User', path: `${SERVER_MAIN_PROTO_DIR}`, renameTo: moveToSrcMainProtoDir, templates: ['entity/user_.proto'], }, ]; const javaFiles = [ { condition: (ctx) => ctx.entityClass === 'Authority', path: `${SERVER_MAIN_SRC_DIR}_package_`, renameTo: moveToJavaPackageSrcDir, templates: ['web/grpc/service/AuthorityGrpcService_.java', 'web/grpc/mapper/AuthorityProtoMapper_.java'], }, { condition: (ctx) => ctx.entityClass === 'User', path: `${SERVER_MAIN_SRC_DIR}_package_`, renameTo: moveToJavaPackageSrcDir, templates: ['web/grpc/service/UserGrpcService_.java', 'web/grpc/mapper/UserProtoMapper_.java'], }, { condition: (ctx) => !defaultEntities.includes(ctx.entityClass ?? ''), path: `${SERVER_MAIN_SRC_DIR}_package_`, renameTo: moveToJavaPackageSrcDir, templates: ['web/grpc/service/_entityClass_GrpcService.java', 'web/grpc/mapper/_entityClass_ProtoMapper.java'], }, ]; export const grpcFiles = { protoFiles, javaFiles, }; export function cleanupEntitiesTask() { } /** * Break protobuf import cycles: * 1) Self-import: never import the current entity's own .proto (e.g. otherEntityFileName was undefined). * 2) Bidirectional embeds (A <-> B both use nested *Proto): on the lexicographically larger entity, * emit a scalar FK field instead of embedding the other message so only one file imports the other. */ function applyGrpcProtoAcyclicRelationships(entities) { const list = entities.filter(e => !e.skipServer); for (const e of list) { for (const r of e.relationships ?? []) { delete r.grpcUseScalarFk; delete r.grpcFkProtoType; } } const relationshipEmbedsOtherProto = (rel) => { if (rel.relationshipType === 'many-to-many' && rel.relationshipSide === 'left') return true; if (rel.relationshipType === 'many-to-one') return true; if (rel.relationshipType === 'one-to-one' && rel.ownerSide) return true; return false; }; const entityKey = (ent) => _.snakeCase(ent.entityClass ?? ent.name).toLowerCase(); const otherKey = (rel) => { if (String(rel.otherEntityName).toLowerCase() === 'user') return 'user'; return _.snakeCase(rel.otherEntityName).toLowerCase(); }; const embeds = (fromKey, toKey) => { const ent = list.find(e => entityKey(e) === fromKey); if (!ent?.relationships) return false; return ent.relationships.some((r) => relationshipEmbedsOtherProto(r) && otherKey(r) === toKey); }; const known = new Set(list.map(entityKey)); for (const entity of list) { const ek = entityKey(entity); for (const rel of entity.relationships ?? []) { if (!relationshipEmbedsOtherProto(rel)) continue; const ok = otherKey(rel); if (ek === ok) continue; if (!known.has(ok)) continue; if (embeds(ek, ok) && embeds(ok, ek) && ek > ok) { rel.grpcUseScalarFk = true; const other = rel.otherEntity; const pk = other?.primaryKey; let grpcFkProtoType = 'string'; if (pk?.typeLong || pk?.fields?.some(f => f.fieldType === 'Long')) { grpcFkProtoType = 'int64'; } else if (pk?.fields?.some(f => f.fieldType === 'Integer')) { grpcFkProtoType = 'int32'; } rel.grpcFkProtoType = grpcFkProtoType; } } } } export default asWritingEntitiesTask(async function writeEntitiesTasks({ application, entities }) { if (application.enableGrpc) { applyGrpcProtoAcyclicRelationships(entities); // Shared gRPC pieces (util/*.proto, ProtobufMapper, ProtoMapper, ProtoValidateUtils, error handlers) are normally // written by writeGrpcFilesTask on initial app gen. If the project was created with enableGrpc:false and later // enabled, entity regen must still emit these or *ProtoMapper and protoc will fail. await this.writeFiles({ sections: { grpcEntitySupport: GRPC_ENTITY_SUPPORT_FILE_DEFS }, context: application, }); for (const entity of entities.filter(e => !e.skipServer)) { const entityWithClass = entity; const instanceType = entityWithClass.dto === 'mapstruct' ? `${entityWithClass.entityClass}DTO` : entityWithClass.entityClass; const instanceName = entityWithClass.dto === 'mapstruct' ? `${entityWithClass.entityInstance}DTO` : entityWithClass.entityInstance; const entityUnderscoredName = _.snakeCase(entityWithClass.entityClass ?? '').toLowerCase(); let idProtoWrappedType; let idProtoType; idProtoType = 'string'; idProtoWrappedType = 'StringValue'; const newApplication = { ...application, instanceType, instanceName, entityUnderscoredName, idProtoType, idProtoWrappedType, }; await this.writeFiles({ sections: grpcFiles, context: { ...newApplication, ...entity }, }); } } });