Skip to content

Feature ETP-3354: Dynamic DAS mapping layer#223

Open
sebastianbarrozo wants to merge 53 commits intodevelopfrom
feature/ETP-3354
Open

Feature ETP-3354: Dynamic DAS mapping layer#223
sebastianbarrozo wants to merge 53 commits intodevelopfrom
feature/ETP-3354

Conversation

@sebastianbarrozo
Copy link
Copy Markdown
Contributor

Summary

  • Dynamic runtime metadata service replacing code generation for DAS
  • Generic DTO converter with strategy pattern for field mapping (DirectMapping, ConstantValue, ComputedMapping, EntityMapping, JavaMapping, JsonPath)
  • Generic repository layer with CRUD operations using Hibernate metamodel
  • REST controller with GET/POST/PUT endpoints, batch support and json_path extraction
  • X-Ray conditional logging for modules in development
  • Caffeine cache for metadata with 500 entries, 24h TTL

Test plan

  • Unit tests for DynamicMetadataService
  • Unit tests for DynamicDTOConverter and field conversion strategies
  • Unit tests for DynamicRepository and EntityClassResolver
  • Unit tests for DynamicRestController, DynamicEndpointRegistry and ExternalIdTranslationService
  • Integration test with a module in development

🤖 Generated with Claude Code

sebastianbarrozo and others added 30 commits February 5, 2026 22:05
Phase 1: Dynamic Metadata Service
- Standard stack identified (Spring Boot 3 + Caffeine cache)
- Architecture patterns documented (service layer, records, @Cacheable)
- Pitfalls catalogued (N+1 queries, lazy loading, self-invocation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 01: Dynamic Metadata Service
- 2 plan(s) in 2 wave(s)
- 1 parallel (wave 1), 1 sequential (wave 2 depends on wave 1)
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split Plan 01-01 into 01-01 (models+interface) and 01-02 (service impl).
Renumbered test plan to 01-03. Removed non-autonomous Task 2 from test
plan and merged verification into Task 1. Added testPreloadCache test.
Fixed must_haves wording. Removed ambiguous conditional guidance.
Updated wave assignments and dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Caffeine and spring-boot-starter-cache dependencies to build.gradle
- Create FieldMappingType enum with four mapping types (DM, JM, CV, JP)
- Create immutable FieldMetadata record for field mapping configuration
- Create immutable EntityMetadata record for projection entity metadata
- Create immutable ProjectionMetadata record with entity lookup helper

These foundational types establish the metadata model for the dynamic DAS system.
All records are immutable for safe caching.
- Define public API for metadata queries: getProjection, getProjectionEntity, getFields
- Add getAllProjectionNames for listing all registered projections
- Add invalidateCache for manual cache invalidation
- Interface includes comprehensive Javadoc for each method

This establishes the API contract that the service implementation (Plan 02) will fulfill.
Tasks completed: 2/2
- Task 1: Add cache dependencies and create metadata models
- Task 2: Create DynamicMetadataService interface

SUMMARY: .planning/phases/01-dynamic-metadata-service/01-01-SUMMARY.md
- Added MetadataCacheConfig with @EnableCaching
- Configured projections and projectionsByName caches
- Set 500 max entries, 24hr expiration, stats recording
- Uses Caffeine with Spring Cache abstraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Service with EntityManager and CacheManager injection
- preloadCache() method with @eventlistener(ApplicationReadyEvent)
- JPQL queries with JOIN FETCH for projections + entities + fields
- Hibernate.initialize() for lazy relationships
- @Cacheable on getProjection(String name)
- JPA-to-record conversion: toProjectionMetadata/toEntityMetadata/toFieldMetadata
- FieldMappingType.fromCode() usage with error handling
- getProjectionEntity delegates to getProjection().flatMap()
- getFields iterates cache, falls back to DB query
- getAllProjectionNames returns cache keySet
- @CacheEvict on invalidateCache()
- unwrapCacheValue and loadFieldsFromDb helper methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Create MetadataCacheConfig
- Create DynamicMetadataServiceImpl

SUMMARY: .planning/phases/01-dynamic-metadata-service/01-02-SUMMARY.md
- Test projection loading and caching
- Test all four field mapping types (DM, JM, CV, JP)
- Test cache invalidation
- Test entity navigation
- Test field retrieval from cache and database
- Test preload functionality
- Test getAllProjectionNames
- 15 test methods covering all scenarios

Note: Tests cannot be executed due to pre-existing compilation issues
in the entities module (unrelated to this work). Tests are structurally
correct and ready to run once compilation issues are resolved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 1/1
- Created DynamicMetadataServiceTest with 15 comprehensive test methods

SUMMARY: .planning/phases/01-dynamic-metadata-service/01-03-SUMMARY.md

Note: Tests cannot be executed due to pre-existing project-wide compilation
issues in entity code generation (unrelated to this work). Tests are
structurally correct and ready to run once compilation blockers resolved.
Includes diagnostic tools

Phase 1 (Dynamic Metadata Service) complete and verified (14/14 must-haves).
Added MetadataDiagnosticController with HTML explorer, JWT testing, Postman
export, cache reload, and system info modal. Fixed FreeMarker template
compilation issues. Added EM/CM field mapping types, in-development module
filter, and DbPublicKeyInitializer for local JWT validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 02: Generic DTO Converter
- Implementation decisions documented
- Phase boundary established
- Portability strategy: develop in RX, reimplement in Classic
- Key decisions: Hibernate PropertyAccessor, nested EM objects,
  externalId for writes, compatible type coercion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 02: Generic DTO Converter
- Standard stack identified (Spring, Hibernate 6, BeanUtils)
- Architecture patterns documented (Strategy per FieldMappingType)
- Pitfalls catalogued (null handling, cycles, type coercion, audit fields)
- Code examples from generated converters analyzed
Phase 02: Generic DTO Converter
- 3 plans in 3 waves
- Wave 1: Foundation (strategy interface, PropertyAccessor, ConversionContext, DM/CV/CM strategies)
- Wave 2: Complex strategies (EM, JM, JP) + DynamicDTOConverter orchestrator
- Wave 3: Unit tests for converter and strategies
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add BeanUtils dependency for nested property access

- Add Apache Commons BeanUtils 1.9.4 dependency to build.gradle
- Create FieldConversionStrategy interface with readField/writeField contracts
- Create ConversionContext for cycle detection with isVisited() method
- Create ConversionException for conversion error handling
- Create PropertyAccessorService using BeanUtils for nested property access
- Support for fullDto field in ConversionContext for JM write strategies
DirectMapping, ConstantValue, and ComputedMapping strategies

- Create DirectMappingStrategy with readField using getNestedProperty + handleBaseObject
- DirectMappingStrategy writeField handles Date/numeric type coercion
- Create ConstantValueStrategy using mappingUtils.constantValue() for reads
- ConstantValueStrategy writeField is no-op (constants are read-only)
- Create ComputedMappingStrategy delegating to ConstantValueStrategy
- All strategies are @component Spring beans implementing FieldConversionStrategy
Tasks completed: 2/2
- Task 1: Create converter foundation classes and add BeanUtils dependency
- Task 2: Create DirectMapping, ConstantValue, and ComputedMapping strategies

SUMMARY: .planning/phases/02-generic-dto-converter/02-01-SUMMARY.md
…gies

- EntityMappingStrategy: recursive entity conversion with cycle detection,
  handles both many-to-one and one-to-many (Collection), resolves write
  references via ExternalIdService, uses @lazy to break circular dependency
  with DynamicDTOConverter
- JavaMappingStrategy: delegates read to DTOReadMapping and write to
  DTOWriteMapping beans resolved by Spring qualifier from field metadata,
  passes full DTO from ConversionContext for write operations
- JsonPathStrategy: extracts values from JSON string properties using
  com.jayway.jsonpath.JsonPath.read(), applies MappingUtils.handleBaseObject()
  for type coercion, write operations are no-op (read-only strategy)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bidirectional entity-to-DTO conversion

- Strategy map wiring all 6 FieldMappingType values to their implementations
- convertToMap: Entity -> Map using field metadata and ConversionContext for
  cycle detection propagation across recursive EM conversions
- convertToEntity: Map -> Entity with mandatory field validation (excluding
  CV/CM constants), field population via strategy dispatch, and automatic
  audit field integration via AuditServiceInterceptor
- Entity instantiation via AD_Table.javaClassName JPQL lookup with
  ConcurrentHashMap cache for repeated lookups
- findEntityMetadataById helper for EM strategy to resolve related entity
  metadata by scanning all projections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… plan

Tasks completed: 2/2
- Task 1: EntityMapping, JavaMapping, and JsonPath strategies
- Task 2: DynamicDTOConverter orchestrator

SUMMARY: .planning/phases/02-generic-dto-converter/02-02-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… unit tests

- DirectMappingStrategy: 7 tests covering read/write, null handling, nested paths, Date coercion
- EntityMappingStrategy: 6 tests covering cycle detection stub, recursive conversion, ExternalId resolution
- Tests use @ExtendWith(MockitoExtension.class), AAA pattern, descriptive method names
- Matches project test conventions from DynamicMetadataServiceTest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 14 tests: convertToMap (6) and convertToEntity (8)
- Strategy routing: DM, CV, JM delegation verified
- Mandatory validation: throws ConversionException, excludes CV/CM fields
- Audit integration: setAuditValues called for BaseRXObject, skipped for plain Object
- Graceful degradation: strategy exceptions result in null field values
- ConversionContext fullDto propagation verified via ArgumentCaptor
- Field order preservation via LinkedHashMap assertion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Task 1: DirectMappingStrategy (7 tests) and EntityMappingStrategy (6 tests)
- Task 2: DynamicDTOConverter orchestrator (14 tests)

SUMMARY: .planning/phases/02-generic-dto-converter/02-03-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BaseRXObject does not define getId() — the id field lives on each
generated entity subclass. Use PropertyAccessorService to read it
dynamically. Also mark Phase 2 plans complete in ROADMAP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 03: Generic Repository Layer
- Implementation decisions documented
- Phase boundary established
- CRUD, entity resolution, transactions, ExternalId flow covered

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3: Generic Repository Layer
- Standard stack identified (EntityManager, CriteriaBuilder, Jakarta Validator)
- Architecture patterns documented (entity class resolution, dynamic filtering, save/update flow)
- Exact order of operations from BaseDTORepositoryDefault analyzed
- Pitfalls catalogued (persist vs merge, double save, validator id skip, external id timing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 03: Generic Repository Layer
- 2 plan(s) in 2 wave(s)
- Wave 1: EntityClassResolver + DynamicRepository (CRUD/batch/pagination)
- Wave 2: Unit tests for repository layer
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address 5 checker issues:
- BLOCKER: Pre-instantiate new entities via EntityClassResolver before
  passing to converter (avoids AD_Table.javaClassName path)
- BLOCKER: Remove duplicate auditService.setAuditValues() call from
  repository (converter already handles it internally)
- WARNING: Split DynamicRepository into read ops (Task 2) and write
  ops (Task 3) for scope management
- WARNING: Reframe implementation-focused must_haves truth to
  user-observable behavior
- WARNING: Document convertExternalToInternalId as Phase 4 controller
  responsibility (FR-5 partially covered)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eption

- EntityClassResolver scans Hibernate metamodel at startup via @eventlistener(ApplicationReadyEvent)
- Builds ConcurrentHashMap indexes by @table(name) and static TABLE_ID field
- resolveByTableId() and resolveByTableName() with DynamicRepositoryException on miss
- DynamicRepositoryException extends RuntimeException with message and cause constructors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sebastianbarrozo and others added 11 commits February 6, 2026 19:49
- Translates top-level "id" field from external to internal ID
- Translates ENTITY_MAPPING reference fields using related entity tableId
- Handles both String IDs and nested Map objects with "id" key
- Constructor-injects ExternalIdService and DynamicDTOConverter
- Mutates DTO map in place for controller-level ID translation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Logs all dynamic endpoints at startup via @eventlistener(ApplicationReadyEvent)
- Entities with restEndPoint=false logged at DEBUG level, not registered
- isRestEndpoint validates entity REST access by projection + externalName
- resolveEntityByExternalName returns Optional<EntityMetadata> for URL resolution
- Matches externalName first, falls back to entity name if externalName is null

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Create ExternalIdTranslationService
- Create DynamicEndpointRegistry

SUMMARY: .planning/phases/04-generic-rest-controller/04-01-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add @RestController with @RequestMapping("/{projectionName}/{entityName}")
- Implement resolveEntityMetadata helper for validation and entity lookup
- Add findAll endpoint with pagination, sorting, and filter support
- Add findById endpoint with EntityNotFoundException -> 404 mapping
- Constructor-inject DynamicRepository, DynamicEndpointRegistry, ExternalIdTranslationService

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… support

- Add POST endpoint with Jayway JsonPath parsing (exact BindedRestController pattern)
- Support JSONArray batch creation via saveBatch repository method
- Add PUT endpoint with path ID override and ObjectMapper parsing
- External ID translation applied before repository delegation
- Error handling: JsonProcessingException -> 400, ResponseStatusException -> rethrow
- POST returns 201 for both single and batch, PUT returns 201 (matches BindedRestController)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Create DynamicRestController with GET endpoints
- Add POST and PUT endpoints with json_path and batch support

SUMMARY: .planning/phases/04-generic-rest-controller/04-02-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ExternalIdTranslationService and DynamicEndpointRegistry tests

- ExternalIdTranslationServiceTest: 8 tests covering id translation, EM field translation (String/Map), skip logic, passthrough, multiple fields
- DynamicEndpointRegistryTest: 8 tests covering entity resolution by externalName, name fallback, restEndPoint gating, startup logging
…ethods

- GET findAll: paginated results, filter param cleanup, 404 for missing projection, 404 for restEndPoint=false
- GET findById: success 200, 404 not found, 404 restEndPoint=false
- POST create: single 201, batch 201, json_path extraction, empty body 400, default json_path, translateExternalIds
- PUT update: 201 status, id from path variable, translateExternalIds called
Tasks completed: 2/2
- Task 1: ExternalIdTranslationServiceTest (8 tests) and DynamicEndpointRegistryTest (8 tests)
- Task 2: DynamicRestControllerTest (16 tests)

SUMMARY: .planning/phases/04-generic-rest-controller/04-03-SUMMARY.md
Phase 4 verified: 9/9 must-haves passed
- ExternalIdTranslationService (180 lines, 8 tests)
- DynamicEndpointRegistry (140 lines, 8 tests)
- DynamicRestController (296 lines, 16 tests)
- Total: 32 unit tests across 3 test classes

VERIFICATION: .planning/phases/04-generic-rest-controller/04-VERIFICATION.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…me handling

- Add conditional X-Ray debug logging in controller, repository and converter
- Fix MultipleBagFetchException by splitting entity+field loading into two queries
- Use JOIN FETCH for module to avoid lazy loading issues
- Remove unnecessary .toUpperCase() on projection names
- Add moduleInDevelopment flag to EntityMetadata and ProjectionMetadata
- Update tests to match new metadata model signatures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

externalIdTranslationService.translateExternalIds(dto, entityMeta);

Map<String, Object> result = repository.update(

Check failure

Code scanning / SnykCode

Unsafe Reflection Error

Unsanitized input from {0} {1} into {2}, where it is used for Reflection. This may result in an Unsafe Reflection vulnerability.
public ResponseEntity<Object> create(
@PathVariable String projectionName,
@PathVariable String entityName,
@RequestBody String rawEntity,

Check notice

Code scanning / SnykCode

Spring Cross-Site Request Forgery (CSRF) Note

The {0} parameter is vulnerable to Cross Site Request Forgery (CSRF) attacks due to not using Spring Security. This could allow an attacker to execute requests on a user's behalf. Consider including Spring Security's CSRF protection within your application.
@PathVariable String projectionName,
@PathVariable String entityName,
@PathVariable String id,
@RequestBody String rawEntity) {

Check notice

Code scanning / SnykCode

Spring Cross-Site Request Forgery (CSRF) Note

The {0} parameter is vulnerable to Cross Site Request Forgery (CSRF) attacks due to not using Spring Security. This could allow an attacker to execute requests on a user's behalf. Consider including Spring Security's CSRF protection within your application.
@etendobot
Copy link
Copy Markdown
Collaborator

Warning

Git Police 👮

One or more commit messages do not meet the required standards. Please correct them.

For more information, visit: Methodology for repository managment

Copy link
Copy Markdown
Collaborator

@etendobot etendobot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested by agent. Please resolve blocking issues.

- Refactor DynamicRestController, ExternalIdTranslationService and
  DynamicMetadataServiceImpl into smaller private methods
- Extract string constants in DynamicRepository and GenerateProtoFile
- Remove unused auditService from DynamicRepository and its test
- Disable static mapping code generation in GenerateEntities
- Remove unused fields and parameters in GenerateProtoFile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
String entityName) {
logBatchStart(entityMeta, projectionName, entityName, rawDataArray.size());
List<Map<String, Object>> dtos = convertArrayToDtos(rawDataArray, entityMeta);
List<Map<String, Object>> results = repository.saveBatch(

Check failure

Code scanning / SnykCode

Unsafe Reflection Error

Unsanitized input from {0} {1} into {2}, where it is used for Reflection. This may result in an Unsafe Reflection vulnerability.
String entityName) {
logSingleEntityStart(entityMeta, projectionName, entityName);
externalIdTranslationService.translateExternalIds(dto, entityMeta);
Map<String, Object> result = repository.save(dto, projectionName, entityMeta.name());

Check failure

Code scanning / SnykCode

Unsafe Reflection Error

Unsanitized input from {0} {1} into {2}, where it is used for Reflection. This may result in an Unsafe Reflection vulnerability.
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> dto = objectMapper.readValue(rawEntity, Map.class);
externalIdTranslationService.translateExternalIds(dto, entityMeta);
Map<String, Object> result = repository.save(dto, projectionName, entityMeta.name());

Check failure

Code scanning / SnykCode

Unsafe Reflection Error

Unsanitized input from {0} {1} into {2}, where it is used for Reflection. This may result in an Unsafe Reflection vulnerability.
@sonarscanetendo
Copy link
Copy Markdown

Failed Quality Gate failed

  • E Security Rating on New Code (is worse than A)
  • 214 New Issues (is greater than 0)

Project ID: etendosoftware_etendo_rx_AYOKxNe4uJ79WHyLB97w

View in SonarQube

}

private boolean shouldTranslateField(FieldMetadata field, Map<String, Object> dto) {
return field.fieldMapping() == FieldMappingType.ENTITY_MAPPING
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Consider renaming 'shouldTranslateField' to 'isEligibleForTranslation' for better clarity.

private boolean isEligibleForTranslation(FieldMetadata field, Map<String, Object> dto) {

Rationale: Renaming the method to 'isEligibleForTranslation' better conveys the intent of checking conditions before proceeding with the action.


private boolean shouldTranslateField(FieldMetadata field, Map<String, Object> dto) {
return field.fieldMapping() == FieldMappingType.ENTITY_MAPPING
&& dto.get(field.name()) != null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Add a 'containsKey' check before 'get' for defensive consistency.

return field.fieldMapping() == FieldMappingType.ENTITY_MAPPING 
            && dto.containsKey(field.name()) 
            && dto.get(field.name()) != null;

Rationale: Explicitly checking 'containsKey' is a defensive programming best practice when working with Map-based DTOs to distinguish between a missing key and a null value.

public final static String GENERATED_DIR = "/../build/tmp/generated";
public static final String GENERATED_DIR = "/../build/tmp/generated";
private static final String ENTITY_SCAN_TEMPLATE = "/org/openbravo/base/gen/entityscan.ftl";
private static final Logger log = LogManager.getLogger();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Good use of constants for template paths.

private static final String ENTITY_SCAN_TEMPLATE = "/org/openbravo/base/gen/entityscan.ftl";

Rationale: Extracted a hardcoded string into a private static final constant. This improves maintainability and follows standard Java practices for reusable strings.

log.error("JSON processing error while creating entity", e);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JSON format");
} catch (ResponseStatusException e) {
throw e;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Simplify the exception handling by combining or restructuring the catch blocks.

        } catch (Exception e) {
+            if (e instanceof ResponseStatusException) {
+                throw e;
+            }
+            log.error("Error while creating entity", e);
+            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
+        }

Rationale: While the current implementation works, catching and re-throwing a specific exception just to catch its parent afterwards is slightly redundant. Simplifying the catch block improves readability.

}

private ResponseEntity<Object> processFallbackCreation(String rawEntity,
EntityMetadata entityMeta,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Define ObjectMapper as a static final constant instead of instantiating it inside the method.

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private ResponseEntity<Object> processFallbackCreation(String rawEntity, 
                                                           EntityMetadata entityMeta,
                                                           String projectionName, 
                                                           String entityName) 
            throws JsonProcessingException {
        Map<String, Object> dto = objectMapper.readValue(rawEntity, Map.class);

Rationale: ObjectMapper is thread-safe and expensive to initialize. Reusing a single instance as a class constant is a standard performance best practice in Spring/Java applications.

}

private void processProjection(ETRXProjection projection, Cache cache) {
try {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Add a null check for the projection name before putting it in the cache.

private void processProjection(ETRXProjection projection, Cache cache) {
        try {
            initializeProjectionRelationships(projection);
            ProjectionMetadata metadata = toProjectionMetadata(projection);
            if (projection.getName() != null) {
                cache.put(projection.getName(), metadata);
                log.debug("Preloaded projection: {}", projection.getName());
            } else {
                log.warn("Projection name is null for ID: {}, skipping cache", projection.getId());
            }
        } catch (Exception e) {
            log.error("Failed to preload projection: {}", projection.getName(), e);
        }
    }

Rationale: While loadProjectionsFromDatabase filters for projections in development, it is defensive to check for null names before using them as cache keys to prevent NullPointerException or inconsistent cache state.

public class GenerateProtoFile {
private static final Logger log = LogManager.getLogger();
private static final String MOBILESYNC_DTO_PATH = "/modules/com.etendorx.integration.mobilesync/src-gen/main/java/com/etendorx/integration/mobilesync/dto";
private static final String MOBILESYNC_ENTITIES_PACKAGE = "com.etendorx.integration.mobilesync.entities";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Verify if these paths should remain hardcoded or if they could eventually be parameterized via gradle.properties or a configuration file to support custom integration modules.

private static final String MOBILESYNC_DTO_PATH = "/modules/com.etendorx.integration.mobilesync/src-gen/main/java/com/etendorx/integration/mobilesync/dto";

Rationale: Centralizing these paths as constants is a good practice as they are used across multiple generator methods. This improves maintainability if the module structure changes.

* @param templatePath
* @param prefix
* @param sufix
* @throws FileNotFoundException
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Update the Javadoc to remove the @param tags for the removed parameters (computedColumns, includeViews, prefix).

  /**
   * ...
   * @param pathEtendoRx
   * @param projection
   * @param repositories
   * @param sourcefilePath
   * @param templatePath
   * @param sufix
   * @throws FileNotFoundException
   */
  private void generateSourcefile(String pathEtendoRx, Projection projection,
      List<HashMap<String, Object>> repositories,
      String sourcefilePath, String templatePath, String sufix)

Rationale: The Javadoc parameters no longer match the method signature after the refactoring. Removing unused @param tags improves documentation accuracy.


public void setEntitiesModelMap(Map<String, Entity> entitiesModelMap) {
this.entitiesModelMap = entitiesModelMap;
this.entitiesModelMap = MetadataUtil.generateEntitiesMap(entitiesModel);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Ensure that the 'entitiesModel' list is not required by any other logic (e.g., templates) before finalizing the removal.

  public void setEntitiesModel(List<Entity> entitiesModel) {
    this.entitiesModelMap = MetadataUtil.generateEntitiesMap(entitiesModel);
  }

Rationale: Removing the unused 'entitiesModel' list and its setter simplifies the class state, as only the map is required for entity lookups during generation.

public class DynamicRepository {

private static final String ENTITY_NOT_FOUND_MESSAGE = "Entity metadata not found for projection: ";
private static final String ENTITY_SUFFIX = ", entity: ";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

The introduction of constants for error messages is a good practice.

private static final String ENTITY_NOT_FOUND_MESSAGE = "Entity metadata not found for projection: ";

Rationale: Moving repetitive string literals to constants improves maintainability and follows clean code practices.

}

if (entityMeta.moduleInDevelopment()) {
log.info("[X-Ray] Repository.save | operation={} class={} dtoId={}",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion

Moving the logging block after the entity existence check provides more accurate diagnostic information.

        if (entityMeta.moduleInDevelopment()) {
            log.info("[X-Ray] Repository.save | operation={} class={} dtoId={}",
                isNew ? "INSERT" : "UPDATE", entityClass.getSimpleName(), dtoId);
        }

Rationale: Moving the log block after the ID check ensures that the logged isNew status and existingEntity state are consistent with the actual operation performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants