Skip to content

Add Yaml bean support#2436

Merged
rrayst merged 71 commits into
masterfrom
yaml-bean-support
Dec 18, 2025
Merged

Add Yaml bean support#2436
rrayst merged 71 commits into
masterfrom
yaml-bean-support

Conversation

@christiangoerdes
Copy link
Copy Markdown
Collaborator

@christiangoerdes christiangoerdes commented Dec 11, 2025

Summary by CodeRabbit

  • New Features

    • YAML "components" support with object-level $ref, inline component references and YAML-driven bean definitions (constructor args, properties, singleton/prototype).
    • Generated helper classes for component and bean descriptors to improve tooling outputs.
  • Improvements

    • Configuration model reclassified to use "components" across schema, CRD and tooling.
    • JSON Schema: patternProperties and allOf support; improved component-aware $ref handling.
    • New annotation flags: component, excludeFromFlow and excludeFromJson.
  • Tests

    • Added comprehensive YAML/components and bean parsing test suites.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 11, 2025

Walkthrough

Replaces topLevel with component semantics, adds per-child excludeFromJson() and element excludeFromFlow() flags, introduces component-aware YAML parsing with object-level $ref and top-level components, adds BeanFactory and class generators, extends JSON Schema features, and updates many annotations and tests.

Changes

Cohort / File(s) Summary
Annotations & metadata
annot/src/main/java/.../MCElement.java, annot/src/main/java/.../MCChildElement.java
Added component() and excludeFromFlow() to MCElement; added excludeFromJson() to MCChildElement.
Annotation processor & model API
annot/src/main/java/.../SpringConfigurationXSDGeneratingAnnotationProcessor.java, annot/src/main/java/.../model/MainInfo.java
Replaced topLevel→component checks, renamed getTopLevels()getComponents(), added isComponent(...), adjusted uniqueness/registration and generation ordering to emit component/bean classes earlier.
Class generation framework
annot/src/main/java/.../generator/ClassGenerator.java, .../ComponentClassGenerator.java, .../BeanClassGenerator.java
Added ClassGenerator base and generators to emit Components and Bean descriptor Java sources.
Bean construction & registry
annot/src/main/java/.../bean/BeanFactory.java, annot/src/main/java/.../yaml/BeanRegistryImplementation.java
New BeanFactory builds objects from JsonNode (constructor selection, property injection, ref resolution); registry now uses BeanFactory for bean kind, adjusts prototype vs singleton caching and exception handling.
YAML parsing & refs
annot/src/main/java/.../yaml/GenericYamlParser.java, annot/src/main/java/.../yaml/McYamlIntrospector.java
Parse top-level components, extract component BeanDefinitions, add object-level $ref resolution (applyObjectLevelRef), and refactor setter/key matching and element-name helpers.
JSON Schema & schema model
annot/src/main/java/.../generator/JsonSchemaGenerator.java, annot/src/main/java/.../generator/kubernetes/model/SchemaObject.java
Component-aware JSON Schema support (components map, ID pattern); added patternProperties/allOf; updated list/item/$ref handling and schema helpers.
Parsers, blueprints & helpers
annot/src/main/java/.../generator/BlueprintParsers.java, .../Parsers.java, .../Schemas.java, .../HelpReference.java
Replaced topLevel() checks with component() across parser registration, help/reference and XSD/schema assembly logic.
Kubernetes CRD & YAML generators
annot/src/main/java/.../generator/kubernetes/*
Renamed top-level accessors to component-based variants; switched CRD/schema generation to iterate components; left some list/noEnvelope handling noted as TODO.
Reflection & setter handling
annot/src/main/java/.../yaml/MethodSetter.java, annot/src/main/java/.../util/ReflectionUtil.java
Added collection element-type inference/validation, bean-reference resolution, and ReflectionUtil conversion helpers.
Compiler & test utilities
annot/src/test/java/.../util/CompilerHelper.java, .../StructureAssertionUtil.java
Added parseXML and context ClassLoader helpers; added order-independent assertStructure(List, ...) with backtracking.
Tests added/updated
annot/src/test/java/.../YAMLBeanParsingTest.java, .../YAMLComponentsParsingTest.java, .../YAMLParsingTest.java, .../SpringConfigXSDErrorsTest.java, ...
New and adjusted tests for YAML bean/component parsing; many fixtures updated from topLevel=falsecomponent=false.
Bulk annotation updates (core)
core/src/main/java/... (many files)
Replaced topLevel=falsecomponent=false across numerous types; added excludeFromFlow=true to multiple interceptors/elements.
Removals & cleanups
core/src/main/java/com/predic8/membrane/core/kubernetes/Bean.java, assorted files
Deleted Kubernetes Bean wrapper; removed unused imports and minor cleanups.
Model changes
annot/src/main/java/.../model/ElementInfo.java, annot/src/main/java/.../model/OtherAttributesInfo.java
Reworked ElementInfo public API and helpers; added OtherAttributesInfo map value-type inference and ValueType enum.

Sequence Diagram(s)

sequenceDiagram
    participant YAML as "YAML docs"
    participant Parser as "GenericYamlParser"
    participant Registry as "BeanRegistryImplementation"
    participant Factory as "BeanFactory"
    participant Parent as "Parent object"

    YAML->>Parser: feed documents (may include top-level "components")
    Parser->>Registry: register extracted component BeanDefinitions
    Parser->>Parser: encounter object node (may contain "$ref")
    alt object-level $ref present
        Parser->>Registry: resolveReference($ref)
        Registry->>Factory: create(JsonNode componentBody)
        Factory->>Factory: load class, choose ctor, resolve ctor args (may call Registry)
        Factory-->>Registry: return constructed bean
        Registry-->>Parser: resolved object
        Parser->>Parent: set child property with resolved object
    else inline object
        Parser->>Factory: create(inline object node)
        Factory-->>Parser: populated object
        Parser->>Parent: set child property with populated object
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Areas needing extra attention:

  • SpringConfigurationXSDGeneratingAnnotationProcessor: component/top-level semantics, uniqueness and generation ordering.
  • GenericYamlParser, BeanFactory, BeanRegistryImplementation: object-level $ref resolution, lifecycle (prototype vs singleton), reflection-based instantiation and error handling.
  • JsonSchemaGenerator & SchemaObject: patternProperties/allOf composition and component references.
  • MethodSetter & McYamlIntrospector: collection element-type inference and setter resolution.
  • Large-scale annotation updates in core: ensure consistent component vs topLevel and excludeFromFlow usage.

Possibly related PRs

Suggested labels

7.x

Suggested reviewers

  • predic8

Poem

🐇
I hopped through YAML, nibbling refs and names,
Sprouted little beans and wired up their frames.
Parsers fetched my carrots, factories baked the rest,
Tests cheered as mappings settled into nest.
I thumped my paw — the build passed its quest.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective of the changeset: adding YAML bean support. It is specific, concise, and directly related to the primary changes across annotation processors, generators, and test files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch yaml-bean-support

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf7eb0d and a31ddba.

📒 Files selected for processing (1)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@membrane-ci-server
Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

When generating JSON schema for top-level @mcelement classes, enforce that any setId method is exactly setId(String), that at most one exists, and that it is annotated with @MCAttribute using the name "id" (or default). Throw ProcessingException on violations. If no @MCAttribute("id") is present, add an implicit optional "id" string property to the top-level schema.
…hema; extract bean id from YAML and fix bean index increment

- Comment out early return for cei.excludedFromJsonSchema() so child elements are still added to schema (avoids validation failures)
- Add extractIdOrDefault to GenericYamlParser to use an explicit "id" from the bean node when present
- Use kind variable and correct idx++ placement when creating BeanDefinition entries
@christiangoerdes
Copy link
Copy Markdown
Collaborator Author

/ok-to-test

christiangoerdes and others added 22 commits December 11, 2025 16:39
Add a boolean rootDef() to MCElement (with javadoc) to mark elements allowed at the config root. Mark APIProxy as a root-def element. Add new com.predic8.membrane.core.kubernetes.Components class annotated as a root-def element to represent top-level components with a child elements list and getter/setter.
…move legacy bean; mark Components noEnvelope; relax noEnvelope/topLevel check

- Rename shouldGenerateParserType to shouldGenerateFlowParserType and update all call sites in JsonSchemaGenerator
- Filter root definitions using rootDef() when generating JSON schema top-level defs
- Adjust parser creation logic for flow parser refs
- Remove legacy kubernetes bean field and its getter/setter from Bean.java
- Mark Components as noEnvelope = true on MCElement
- Comment out strict check in SpringConfigurationXSDGeneratingAnnotationProcessor for noEnvelope/topLevel and add TODO notes
…ons in Components

- Introduce `allOf` field and related functionality in SchemaObject for JSON schema generation.
- Update Components class to adjust MCElement annotations by commenting out `noEnvelope` attribute.
…n-support

# Conflicts:
#	annot/src/test/java/com/predic8/membrane/annot/SpringConfigXSDErrorsTest.java
…ents lists

Add createComponentParser and call ensureValidIdSetter for component-only elements.
Make components without a real id gain an optional "id" property; alias schemas
for no-envelope or elements with a real id. Update addChildsAsProperties to use
component-specific $defs names when in a components list context. Add helpers:
isComponentsList, componentDefName, hasRealIdAttribute.
current use:
components:
  - basicAuthentication:
      id: asd
---
api:
  port:  2000
  flow:
    - basicAuthentication:
        $ref: asd

------------------------------------------------------

Generate component-or-ref schema definitions and use them for components

- Add createComponentOrRefParser(Model, ElementInfo) to produce a oneOf schema that accepts either the inline component object or a {$ref: string}; handle noEnvelope case by returning a simple ref for now.
- Add componentOrRefDefName helper to name "OrRef" defs.
- Register component-or-ref parser for each element definition.
- Update component name resolution: when an element is a component, use componentDefName in components context, otherwise use componentOrRefDefName so references point to the new oneOf definition.
- Adjust list child processing to reference the OrRef definition for component children.
Add support for referencing a component instance directly at the list-item
level (allow "- $ref: ...") when the list can contain component types.
Make component schemas simpler: only the components list gets the id-augmented
schema name, remove the separate createComponentOrRefParser and related
helpers, and simplify createComponentParser behavior. Update addChildsAsProperties
signature and adjust schema property requirements accordingly.
…tes value types

- Change Components to store components as Map<String,Object> and use @MCOtherAttributes setter.
- Extend OtherAttributesInfo to detect Map value type (String or Object) and expose ValueType enum.
- In JsonSchemaGenerator, treat @MCOtherAttributes with non-string values as an object map and create a special components parser that uses patternProperties with anyOf variants referencing $defs for component types. Only allow additionalProperties when otherAttributes map values are String.
- Add SchemaObject.patternProperties support and include it in generated JSON output.
When a type has a child element whose setter expects a component, add an optional
"$ref" string property to the generated object schema so the object can reference
a component via JSON Pointer. Avoid adding the property if a "$ref" already
exists on the parser.

Add hasComponentChild(ElementInfo, MainInfo) to detect component children and
SchemaObject.hasProperty(String) helper to check existing properties.
…element annotations

- Eliminated outdated and unused TODO comments.
- Reintroduced validation for `noEnvelope=true` with conflicting attributes (e.g., `component=true` or `mixed=true`).
…ackage

- Removed redundant getters and setters in `Grammar` and `ElementInfo`.
- Marked `ais` and `ceis` lists in `ElementInfo` as `final` to ensure immutability.
- Cleaned up unused return documentation in `McYamlIntrospector`.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (1)

181-192: Typo in method name: stripFistLinestripFirstLine.

The method name appears to have a typo.

-    static String @NotNull [] stripFistLine(String content) {
+    static String @NotNull [] stripFirstLine(String content) {
♻️ Duplicate comments (6)
annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (2)

21-21: Remove unused import.

The Types import is not used anywhere in this file. While line 149 references Types in a JavaDoc link, the import itself is unnecessary.

-import javax.lang.model.util.Types;

148-155: Missing primitive types in isBeanReference check.

The check excludes int, long, float, double, and boolean primitives but omits char, short, and byte. If a setter takes one of these primitive types, isBeanReference returns true, potentially causing incorrect bean reference resolution attempts.

 private boolean isBeanReference(Class<?> wanted) {
-    if (wanted == Integer.TYPE || wanted == Long.TYPE || wanted == Float.TYPE || wanted == Double.TYPE || wanted == Boolean.TYPE || wanted == String.class)
+    if (wanted.isPrimitive() || wanted == String.class)
         return false;
     return !wanted.isEnum();
 }

Using wanted.isPrimitive() covers all eight primitive types more concisely.

annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (3)

57-57: Remove the outdated TODO comment.

The loadBeanClass method is used in production code (line 47), not just for testing. The TODO comment is misleading.


144-146: Optimize constructor collection.

While the LinkedHashSet prevents duplicates, getConstructors() returns all public constructors, and getDeclaredConstructors() also returns all public ones. This causes redundant iterations. Using only getDeclaredConstructors() with setAccessible(true) (already done at line 165) is sufficient.


240-251: Verify primitive/wrapper compatibility check.

The logic at line 244 checks targetType.isPrimitive() && isWrapperOfPrimitive(targetType, o.getClass()). A previous review flagged this as potentially incorrect. Ensure that ReflectionUtil.isWrapperOfPrimitive accepts (primitiveType, wrapperClass) in that order, and that when targetType is a primitive, o.getClass() returns the corresponding wrapper class.

Run the following script to verify the signature and usage:

#!/bin/bash
# Find the isWrapperOfPrimitive method signature
rg -nP 'isWrapperOfPrimitive\s*\(' --type=java -C3
annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (1)

368-393: Add guard for empty component variants.

If no ElementInfo has component=true (or all are filtered by topLevel), the variants list remains empty at line 392, and anyOf(variants) on line 372 will create an anyOf with no schemas. This may produce invalid JSON Schema.

Apply this diff to add a guard:

 private SchemaObject createComponentsMapParser(Model m, MainInfo main, ElementInfo elementInfo, String parserName) {
     SchemaObject parser = object(parserName)
             .additionalProperties(false)
             .description(getDescriptionContent(elementInfo));
+    var variants = getComponents(m, main);
+    if (variants.isEmpty()) {
+        // No component types declared; allow any object as fallback
+        parser.patternProperty(COMPONENT_ID_PATTERN, object().additionalProperties(true));
+        return parser;
+    }
-    parser.patternProperty(COMPONENT_ID_PATTERN, anyOf(getComponents(m, main)));
+    parser.patternProperty(COMPONENT_ID_PATTERN, anyOf(variants));
     return parser;
 }
🧹 Nitpick comments (2)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (1)

184-196: Consider using a Java record for the Pair class.

The TODO notes merging with a Pair record from core. Since this is a simple immutable data carrier with direct field access, a record would be cleaner:

private record Pair<X, Y>(X x, Y y) {
    public static <X, Y> Pair<X, Y> of(X x, Y y) {
        return new Pair<>(x, y);
    }
}
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java (1)

171-177: Consider using streams for hasProperty.

The linear search is acceptable for typical schema sizes, but could be slightly more idiomatic:

 public boolean hasProperty(String name) {
-    for (AbstractSchema<?> p : properties) {
-        if (name.equals(p.getName()))
-            return true;
-    }
-    return false;
+    return properties.stream().anyMatch(p -> name.equals(p.getName()));
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b104ec and 11290b3.

📒 Files selected for processing (11)
  • annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (1 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (10 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (4 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java (4 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/model/ElementInfo.java (1 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (4 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java (5 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (5 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (7 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (4 hunks)
  • annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
📚 Learning: 2025-10-23T13:04:11.388Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.

Applied to files:

  • annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java
  • annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java
🧬 Code graph analysis (3)
annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (2)
annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (1)
  • GenericYamlParser (37-298)
annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (1)
  • McYamlIntrospector (28-185)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (1)
annot/src/main/java/com/predic8/membrane/annot/generator/ClassGenerator.java (1)
  • ClassGenerator (12-72)
annot/src/main/java/com/predic8/membrane/annot/model/ElementInfo.java (2)
annot/src/main/java/com/predic8/membrane/annot/model/OtherAttributesInfo.java (1)
  • OtherAttributesInfo (24-67)
annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java (1)
  • AnnotUtils (22-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Automated tests
🔇 Additional comments (38)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (4)

17-27: LGTM on import updates.

The imports are well-organized: JetBrains annotations for nullability, Function for the new mapping helpers, and the static import for the shared COPYRIGHT constant from ClassGenerator promotes DRY principles.


50-63: LGTM on extracted helper method.

Good extraction of getSourceFile for better readability. The helper correctly passes originating elements to createSourceFile for proper incremental compilation tracking.


145-175: LGTM on the refactored element mapping generation.

The switch from topLevel() to component() aligns with the PR's goal of introducing component-based semantics. The extracted generateElementMappingString() and generateLocalElementMapping() helpers improve readability and reduce inline complexity.


177-182: No action required. The method getComponentStream is properly defined as a protected method in AbstractGrammar and is correctly available to the Grammar class through inheritance. The implementation filters component elements as expected and the code change is valid.

annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java (3)

31-32: LGTM on new schema composition fields.

Using LinkedHashMap for patternProperties ensures deterministic iteration order in the generated JSON Schema output.


39-59: LGTM on the centralized JSON serialization method.

The json(ObjectNode) method provides a clean orchestration point that:

  1. Calls super.json(node) first to handle base properties
  2. Sets additionalProperties: false only for objects without explicit additional properties
  3. Delegates to focused helper methods for each schema construct

This design supports JSON Schema's allOf, oneOf, and patternProperties keywords correctly.


79-101: LGTM on allOf/oneOf serialization helpers.

Both methods follow the same pattern: null/empty guard, array construction, and proper delegation to each schema's json() method. The separation keeps the code maintainable.

annot/src/main/java/com/predic8/membrane/annot/yaml/MethodSetter.java (3)

111-128: LGTM on collection handling with element-type validation.

The getObjectList method correctly:

  1. Checks if the target type is a Collection
  2. Parses the list using the shared helper
  3. Validates each element against the inferred generic type
  4. Provides descriptive error messages on type mismatch

The null-check on elemType gracefully handles raw types.


130-146: LGTM on bean reference resolution.

The resolveReference helper provides robust error handling:

  • Wraps registry lookup failures in ParsingException with node context
  • Validates the resolved bean's type against the expected parameter type
  • Produces clear error messages including the reference path, actual type, and expected type

174-185: LGTM on generic type extraction.

The getCollectionElementType method correctly handles the common generic patterns:

  • Direct Class<?> type arguments
  • WildcardType with upper bounds (e.g., ? extends Foo)
  • Nested ParameterizedType (e.g., List<Optional<Foo>>)

Returning null for unhandled cases is a safe fallback that skips validation.

annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (2)

81-96: LGTM on refactored YAML/XML parsing with context isolation.

The withContextClassLoader pattern properly isolates the thread context class loader during parsing, which is essential when loading classes from the in-memory compiler output. Both parseYAML and parseXML now follow the same pattern, improving consistency.


98-113: LGTM on the context class loader helper.

The withContextClassLoader method:

  1. Saves the original context class loader
  2. Sets the new class loader in a try block
  3. Restores the original in a finally block (ensuring cleanup even on exceptions)
  4. Uses a ThrowingSupplier to allow checked exceptions from the action

This is a well-structured utility pattern.

annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (3)

34-38: LGTM on the new bean field.

The Javadoc clearly explains the field's purpose. The TODO on line 99 appropriately notes the mutability concern for future consideration.


114-136: LGTM on the new predicate methods.

These predicates provide a clean public API:

  • isComponent() correctly identifies component definitions by the #/components/ JSON pointer prefix
  • isBean() checks for the "bean" kind
  • isDeleted(), isModified(), isAdded() encapsulate action state checks without exposing the internal enum

This design follows the "Tell, Don't Ask" principle by letting callers query state rather than accessing the enum directly.


114-116: Consider null-safety for isComponent().

The method could throw a NullPointerException if name is null (though the constructor validates against null). Adding a null check would make it more defensive:

 public boolean isComponent() {
-    return name != null && name.startsWith("#/components/");
+    return name.startsWith("#/components/");
 }

Actually, looking again, the current code already has name != null && which is correct defensive programming given that the K8S constructor could theoretically receive null from metadata.get("name").asText(). The current implementation is fine.

annot/src/main/java/com/predic8/membrane/annot/yaml/BeanRegistryImplementation.java (5)

77-90: LGTM: Exception handling refactored to use RuntimeException.

The signature no longer declares checked exceptions, and the body wraps all operations in a try-catch that rethrows as RuntimeException. The new path using BeanFactory for "bean" kind aligns with the broader component model introduced in this PR.


120-122: Condition correctly refactored to avoid NPE.

The change from isNotComponent(bd) to !bd.isComponent() resolves the NPE risk flagged in previous reviews. The BeanDefinition.isComponent() method (line 96 in BeanDefinition.java snippet) safely handles null names: return name != null && name.startsWith("#/components/").


163-177: LGTM: Prototype scope handling implemented correctly.

The refactored resolveReference now distinguishes between prototype and singleton beans. Singleton beans are cached in the BeanDefinition, while prototype beans are instantiated on each resolution. The logic is sound and aligns with standard bean lifecycle semantics.


196-203: LGTM: Scope detection logic is correct.

The isPrototypeScope method correctly handles both bean and non-bean types. For beans, it reads the scope field from the JSON node with a default of "SINGLETON", performing a case-insensitive comparison. For non-beans, it delegates to bd.isPrototype().


185-189: LGTM: Component filtering logic is correct.

The filter correctly excludes components (whose names start with #/components/) from the returned bean list, ensuring only non-component beans are included.

annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (1)

42-55: LGTM: Bean creation flow is well-structured.

The create method follows a clear three-step process: load the class, instantiate it with constructor args, and apply properties. The exception handling wraps failures with helpful context about the class being instantiated.

annot/src/main/java/com/predic8/membrane/annot/yaml/McYamlIntrospector.java (4)

72-74: LGTM: Property name matching logic is correct.

The matchesPropertyName method extracts the property name from method names like "setFoo" or "getFoo" and performs a case-insensitive comparison. Since callers ensure the method is a setter (starts with "set"), the substring operation is safe.


85-98: LGTM: Single child setter validation is thorough.

The refactored method adds validation to ensure:

  1. No @MCAttribute setters exist when noEnvelope=true
  2. Exactly one @MCChildElement setter exists
  3. That setter accepts a Collection type

The error messages are clear and helpful.


100-112: LGTM: Child setter collection logic is clear.

The method correctly identifies @MCChildElement setters and validates that exactly one exists, throwing clear exceptions for empty or multiple results.


178-183: LGTM: Element name resolution is well-designed.

The getElementName method provides a sensible fallback hierarchy: first checking the @MCElement annotation's name, then falling back to the class's simple name if the annotation is missing or blank.

annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java (4)

45-48: LGTM: Component ID pattern is reasonable.

The regex pattern ^[A-Za-z_][A-Za-z0-9_-]*$ enforces valid identifier-like names for component IDs, starting with a letter or underscore, followed by alphanumeric characters, underscores, or hyphens.


119-122: LGTM: Components map detection is straightforward.

The check correctly identifies a components map by matching the annotation name and verifying the element is an object type.


144-149: LGTM: Object-level component reference support added correctly.

The code adds a $ref property when the parent element has component children, enabling object-level references to components. The condition ensures $ref is not duplicated if already present.


395-404: LGTM: Component child detection logic is correct.

The method iterates through child element specifications and checks if any child element declaration contains a component type, returning true on the first match.

annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (5)

77-79: LGTM: Component extraction integration is clean.

The code correctly detects top-level "components" sections and delegates to extractComponentBeanDefinitions to process them, aligning with the new component model.


174-177: LGTM: Object-level reference handling is well-integrated.

The code detects a $ref key early in object processing and delegates to the specialized applyObjectLevelRef method, maintaining clean separation of concerns.


202-233: LGTM: Component extraction is thorough and well-validated.

The method:

  1. Handles null/empty components gracefully
  2. Validates the structure is an object
  3. Ensures each component definition has exactly one key (the component type)
  4. Wraps components in a standard top-level node structure
  5. Uses synthetic names with #/components/ prefix for identification

The validation and error handling are appropriate.


240-260: LGTM: Object-level reference resolution is robust.

The method correctly:

  1. Validates the $ref value is a string
  2. Resolves the referenced component
  3. Detects and prevents conflicts between inline definitions and $ref
  4. Injects the referenced component via the appropriate child setter
  5. Provides clear error messages for incompatible types

262-268: LGTM: Reference resolution wrapper provides consistent error handling.

The method wraps the registry's resolveReference call and converts any RuntimeException into a ParsingException with node context, ensuring consistent error reporting across the parsing flow.

annot/src/main/java/com/predic8/membrane/annot/model/ElementInfo.java (4)

78-84: LGTM with note on fallback behavior.

The method searches for a MainInfo by matching output package with config package. If no match is found, it falls back to the first main. Ensure this fallback is appropriate for your use case, as it may return an unexpected MainInfo if packages don't align.


90-93: LGTM: XSD type name transformation is appropriate.

The method replaces dots with underscores to create valid JSON Schema type names, as explained in the comment. This ensures compatibility with JSON Schema parsers.


125-131: LGTM: Type predicates are clear and safe.

The isString and isObject methods safely check the value type by first ensuring oai is non-null, then comparing against the expected ValueType enum values.


134-143: LGTM: Equals and hashCode implementation is correct.

The methods use modern pattern matching and maintain a consistent contract: equality is based on the element reference, and hashCode is derived from the element's qualified name.

@predic8
Copy link
Copy Markdown
Member

predic8 commented Dec 17, 2025

/ok-to-test

- Simplified property-checking logic using streams in `SchemaObject`.
- Corrected method name typo from `stripFistLine` to `stripFirstLine` in `CompilerHelper`.
- Replaced inner `Pair` class with `record` for better conciseness.
- Improved formatting and consistency in generated content for `Grammar`.
…tility methods

- Introduced `ReflectionUtil` for converting string values to specified Java types.
- Added `isWrapperOfPrimitive` method for validating primitive-wrapper type relationships.
- Included comprehensive JUnit tests for all utility methods.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (3)

103-113: Consider preserving exception types in the wrapper.

Wrapping all exceptions in RuntimeException loses the original exception type, which may complicate debugging in test scenarios. Consider using a more specific exception wrapper or rethrowing unchecked exceptions as-is.

 private static <T> T withContextClassLoader(ClassLoader cl, ThrowingSupplier<T> action) {
     ClassLoader original = Thread.currentThread().getContextClassLoader();
     try {
         Thread.currentThread().setContextClassLoader(cl);
         return action.get();
-    } catch (Exception e) {
+    } catch (RuntimeException | Error e) {
+        throw e;
+    } catch (Exception e) {
         throw new RuntimeException(e);
     } finally {
         Thread.currentThread().setContextClassLoader(original);
     }
 }

115-119: Consider extracting common logic with getCompositeClassLoader.

Both xmlClassLoader and getCompositeClassLoader (lines 127-131) follow nearly identical patterns: cast to InMemoryClassLoader, define an overlay with different file extensions, and return a CompositeClassLoader. This duplication could be reduced with a shared helper.

private static CompositeClassLoader overlayClassLoader(CompilerResult cr, String content, String resourcePath) {
    InMemoryClassLoader inMemory = (InMemoryClassLoader) cr.classLoader();
    inMemory.defineOverlay(new OverlayInMemoryFile(resourcePath, content));
    return new CompositeClassLoader(CompilerHelper.class.getClassLoader(), inMemory);
}

Then simplify both methods:

private static CompositeClassLoader xmlClassLoader(CompilerResult cr, String xmlSpringConfig) {
    return overlayClassLoader(cr, xmlSpringConfig, "/demo.xml");
}

private static CompositeClassLoader getCompositeClassLoader(CompilerResult cr, String yamlConfig) {
    return overlayClassLoader(cr, yamlConfig, "/demo.yaml");
}

175-192: Consider clarifying the empty-line-skipping logic.

The while(true) loop with a break statement (lines 183-190) correctly skips leading empty lines, but the flow could be more explicit for readability.

 static String @NotNull [] stripFirstLine(String content) {
-    String[] parts;
-    while (true) {
-        parts = content.split("\n", 2);
+    String[] parts = content.split("\n", 2);
+    while (parts.length == 2 && parts[0].isEmpty()) {
+        content = parts[1];
+        parts = content.split("\n", 2);
+    }
+    if (parts.length != 2) {
-        if (parts.length != 2)
-            throw new RuntimeException("Invalid resource file: %s. The resource is expected to have the format 'resource <path>\n<content>'.".formatted(content));
-        if (!parts[0].isEmpty())
-            break;
-        content = parts[1];
+        throw new RuntimeException("Invalid resource file: %s. The resource is expected to have the format 'resource <path>\n<content>'.".formatted(content));
     }
     return parts;
 }
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (1)

184-189: Address the TODO: Merge with core Pair record.

The TODO at line 184 notes this Pair record duplicates one in core. Consider consolidating to avoid maintenance overhead.

Would you like me to search for the core Pair record and open an issue to track the consolidation?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11290b3 and 2f19129.

📒 Files selected for processing (3)
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (4 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java (4 hunks)
  • annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
📚 Learning: 2025-10-23T13:04:11.388Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.

Applied to files:

  • annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java
🧬 Code graph analysis (1)
annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (1)
annot/src/main/java/com/predic8/membrane/annot/generator/ClassGenerator.java (1)
  • ClassGenerator (12-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Automated tests
🔇 Additional comments (14)
annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (3)

16-39: LGTM!

The import additions appropriately support the new YAML/XML parsing infrastructure and testing utilities.


81-87: LGTM!

The refactoring to use withContextClassLoader eliminates duplication and improves readability while preserving the class loader switching behavior.


98-101: LGTM!

The ThrowingSupplier interface provides a clean pattern for lambda expressions with checked exceptions.

annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/Grammar.java (4)

59-63: LGTM! Clean extraction of source file creation.

The helper method encapsulates file creation logic nicely and passes originating elements for proper incremental compilation tracking.


70-72: Good use of shared COPYRIGHT constant.

Consolidating the copyright notice via static import from ClassGenerator reduces duplication across generators.


74-143: Well-structured text block template.

The template is readable and the formatted() parameters are correctly ordered. The schema location path transformation logic at lines 131-133 correctly maps the Spring package to the JSON resource path.


145-159: The filtering logic correctly separates global and local elements: global elements have component() == true (line 149), while local elements have component() == false (line 156). Verification of the codebase shows consistent usage of the @MCElement annotation with only 2 global components (RedisConnector, XmlConfig) and numerous properly annotated local elements throughout the codebase. The implementation is correct and requires no changes.

annot/src/main/java/com/predic8/membrane/annot/generator/kubernetes/model/SchemaObject.java (7)

31-32: Good choice of LinkedHashMap for patternProperties.

Using LinkedHashMap preserves insertion order, ensuring deterministic JSON output which aids in testing and debugging.


74-77: LGTM! Fluent API for pattern properties.

Consistent with the existing builder pattern used for other properties.


79-101: Consistent implementation of addOneOf and addAllOf.

Both methods follow the same pattern with proper null/empty checks before adding to the node. The logic is correct and symmetric.


103-112: LGTM! Pattern properties serialization.

Correctly iterates over the LinkedHashMap entries and builds the patternProperties JSON object.


114-140: LGTM! Well-documented addProperties method.

The JavaDoc clearly explains the purpose. The logic correctly:

  1. Collects required property names
  2. Builds the properties object
  3. Only emits required array when non-empty

166-173: LGTM! New allOf setter and hasProperty query.

Both methods follow existing conventions. The hasProperty check via stream is clear and concise.


47-59: Verify additionalProperties: false compatibility with allOf/oneOf composition.

Setting additionalProperties: false (line 51) when composing schemas via allOf or oneOf can cause unintended validation failures depending on the JSON Schema validator—properties from referenced sub-schemas may not be recognized as valid properties. Verify this does not conflict with your validator's handling of schema composition.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
annot/src/test/java/com/predic8/membrane/annot/util/ReflectionUtilTest.java (1)

37-40: Use a more specific exception type in assertion.

The assertion catches RuntimeException, which is too broad. The actual exception thrown by Integer.parseInt("abc") is NumberFormatException. Using a more specific exception type improves test precision and makes failures easier to diagnose.

Apply this diff to use the specific exception type:

-        assertThrows(RuntimeException.class, () -> ReflectionUtil.convert("abc", Integer.class));
+        assertThrows(NumberFormatException.class, () -> ReflectionUtil.convert("abc", Integer.class));

Consider expanding test coverage.

The test suite covers basic scenarios but could be enhanced with additional cases:

  • Null-to-primitive conversion (should throw IllegalArgumentException)
  • Character validation with invalid length
  • Enum conversion
  • Other numeric types (long, double, float, short, byte)
  • The isWrapperOfPrimitive method (currently untested)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f19129 and e24a672.

📒 Files selected for processing (2)
  • annot/src/main/java/com/predic8/membrane/annot/util/ReflectionUtil.java (1 hunks)
  • annot/src/test/java/com/predic8/membrane/annot/util/ReflectionUtilTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
🧬 Code graph analysis (1)
annot/src/test/java/com/predic8/membrane/annot/util/ReflectionUtilTest.java (1)
annot/src/main/java/com/predic8/membrane/annot/util/ReflectionUtil.java (1)
  • ReflectionUtil (3-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Automated tests
  • GitHub Check: Analyze (java)
🔇 Additional comments (6)
annot/src/main/java/com/predic8/membrane/annot/util/ReflectionUtil.java (2)

8-34: LGTM!

The conversion logic correctly handles String, primitives, wrappers, char (with length validation), and enum types. The method appropriately throws exceptions for null-to-primitive assignments and unsupported conversions.


44-53: LGTM!

The method correctly covers all 8 primitive/wrapper type pairs and returns an appropriate boolean result.

annot/src/test/java/com/predic8/membrane/annot/util/ReflectionUtilTest.java (4)

10-15: LGTM!

The test correctly validates String conversion behavior.


17-22: LGTM!

The test correctly validates Integer conversion behavior.


24-29: LGTM!

The test correctly validates Boolean conversion behavior.


31-35: LGTM!

The test correctly validates null conversion behavior for non-primitive types.

- Consolidated repetitive class loader overlay logic into a reusable `overlayClassLoader` method.
- Simplified `withContextClassLoader` error handling for better clarity.
- Resolved minor issues in object creation with `APPLICATION_CONTEXT_CLASSNAME` instance lifecycle management.
- Improved imports organization and reduced redundancy in both structure and functionality.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (1)

85-97: Resource leak fixed; ApplicationContext now properly closed.

The previous resource leak concern has been addressed—the ApplicationContext is now correctly closed in the finally block (line 93). The try-finally structure ensures proper cleanup even if an exception occurs.

Optional: Consider API consistency.

parseYAML returns a BeanRegistry while parseXML returns void. If parseXML is intended purely for validation (as the comment suggests), the current design is acceptable. Otherwise, consider returning the ApplicationContext or documenting the asymmetry.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e24a672 and 7edab1a.

📒 Files selected for processing (1)
  • annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Automated tests
  • GitHub Check: Analyze (java)
🔇 Additional comments (3)
annot/src/test/java/com/predic8/membrane/annot/util/CompilerHelper.java (3)

99-111: LGTM! Clean context class loader management.

The implementation correctly:

  • Saves and restores the thread context class loader in a finally block
  • Preserves RuntimeException and Error types by rethrowing them directly
  • Wraps checked exceptions in RuntimeException for easier lambda usage

This is a well-established pattern for temporary class loader context switching.


119-131: LGTM! Good refactoring of class loader creation.

Extracting overlayClassLoader as a common helper eliminates duplication and provides a clear abstraction for creating class loaders with in-memory resource overlays. The specific methods (getCompositeClassLoader, xmlClassLoader) are appropriately focused.


251-254: LGTM! Standard pattern for checked exceptions in lambdas.

ThrowingSupplier is a well-established pattern that enables clean lambda syntax when working with APIs that throw checked exceptions. The interface integrates nicely with withContextClassLoader.

@predic8 predic8 requested a review from rrayst December 17, 2025 17:25
…annotation in `BeanDefinition`, and clarify `BeanFactory` class loading logic
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (2)

121-127: Add null check to prevent NullPointerException.

Unlike parseConstructorArgList (line 114), this method doesn't check for null before calling isArray(), which will throw a NullPointerException if arr is null.

🔎 Apply this diff to add the null check:
 private List<Property> parsePropertyList(JsonNode arr) {
-    if (!arr.isArray()) return List.of();
+    if (arr == null || !arr.isArray()) return List.of();
 
     return StreamSupport.stream(arr.spliterator(), false)
             .map(Property::new)
             .toList();
 }

144-146: Remove duplicate constructor collection.

getConstructors() returns all public constructors, while getDeclaredConstructors() returns all constructors (including public ones). Public constructors will appear twice in the LinkedHashSet, though the Set deduplicates them. Since setAccessible(true) is used at line 165, only getDeclaredConstructors() is needed.

🔎 Apply this diff to use only getDeclaredConstructors:
-    Set<Constructor<?>> constructors = new LinkedHashSet<>();
-    constructors.addAll(Arrays.asList(type.getConstructors()));
-    constructors.addAll(Arrays.asList(type.getDeclaredConstructors()));
+    Constructor<?>[] constructors = type.getDeclaredConstructors();

Also update line 151 to iterate over the array:

-    for (Constructor<?> c : constructors) {
+    for (Constructor<?> c : constructors) {
🧹 Nitpick comments (1)
annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (1)

17-18: Consider using specific imports instead of wildcards.

Wildcard imports can obscure which classes are actually used and may lead to naming conflicts or make refactoring harder.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7edab1a and f94438c.

📒 Files selected for processing (3)
  • annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (1 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (4 hunks)
  • annot/src/test/java/com/predic8/membrane/annot/YAMLBeanParsingTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
📚 Learning: 2025-10-23T13:04:11.388Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.

Applied to files:

  • annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java
🧬 Code graph analysis (1)
annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (1)
annot/src/main/java/com/predic8/membrane/annot/util/ReflectionUtil.java (1)
  • ReflectionUtil (3-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Automated tests
  • GitHub Check: Analyze (java)
🔇 Additional comments (8)
annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java (5)

22-23: LGTM!

The static import of WatchAction constants improves readability.


35-38: LGTM!

Good practice to document the purpose and lifecycle of the bean field.


68-68: LGTM!

Consistent use of the static import for WatchAction constants.


114-136: LGTM!

The new predicate methods provide clear, convenient ways to query the bean definition state:

  • isComponent() safely checks for component references with null protection
  • isBean() identifies bean-kind definitions
  • isDeleted(), isModified(), isAdded() provide clean access to action states

These methods improve code readability and encapsulate the logic for state checks.


111-111: No migration concerns needed — the old annotation key does not exist in the codebase.

The search confirms that "membrane-soa.org/scope" has no references anywhere in the repository, including YAML/configuration files. The code at line 111 appears to be using the new "membrane-api.io/scope" annotation key as a clean, intentional implementation rather than replacing legacy code. This suggests the namespace change (from "membrane-soa.org" to "membrane-api.io") represents a deliberate rebranding of the Membrane project and has been fully transitioned without remnants of the old key.

annot/src/main/java/com/predic8/membrane/annot/bean/BeanFactory.java (2)

220-226: Property name validation has been added.

Good defensive programming - the method now validates the property name is non-null and non-empty before attempting to access charAt(0), addressing the previous review concern.


240-251: Verify primitive/wrapper compatibility logic is correct.

The logic at line 244 checks isWrapperOfPrimitive(targetType, o.getClass()) when targetType.isPrimitive(). Let me trace through a scenario:

  • If targetType is int.class and o is an Integer instance:
    • targetType.isInstance(o) returns false (primitives aren't instance-checkable)
    • targetType.isPrimitive() is true
    • isWrapperOfPrimitive(int.class, Integer.class) should return true
    • The condition !(true && true) evaluates to false, so no exception is thrown

This appears correct, but the logic is somewhat convoluted. A previous review comment suggested this was incorrect, but based on my analysis and the signature of isWrapperOfPrimitive(Class<?> primitive, Class<?> wrapper) from ReflectionUtil, the current implementation seems accurate.

Can you confirm this logic handles all primitive/wrapper combinations correctly? Consider adding a unit test to verify scenarios like:

  • int.class target with Integer reference
  • Integer.class target with Integer reference
  • int.class target with String reference (should throw)
annot/src/test/java/com/predic8/membrane/annot/YAMLBeanParsingTest.java (1)

188-188: No action required. The use of @MCElement(name="holder", topLevel=true) at line 188 is correct and valid.

The topLevel attribute and the component attribute serve different purposes in the @MCElement annotation and are not a replacement for each other. topLevel controls whether an element can be defined at the top-level of config, while component controls whether it can be a separate bean in XML schema or a separate document in YAML/JSON. Both attributes are independently supported and coexist throughout the codebase.

Comment thread annot/src/main/java/com/predic8/membrane/annot/yaml/BeanDefinition.java Outdated
Comment thread annot/src/main/java/com/predic8/membrane/annot/model/OtherAttributesInfo.java Outdated
… import `Scope` in `BeanClassGenerator`.

- Introduced `Scope` enum with `SINGLETON` and `PROTOTYPE` values.
- Replaced `IllegalArgumentException` with `ProcessingException` for better exception handling in `OtherAttributesInfo`.
- Added `Scope` import to `BeanClassGenerator`.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
annot/src/main/java/com/predic8/membrane/annot/generator/Scope.java (1)

3-6: Consider adding JavaDoc to clarify the enum's purpose.

The enum lacks documentation explaining its role in the bean lifecycle and the meaning of each scope constant. Adding JavaDoc would improve maintainability.

🔎 View suggested improvement:
+/**
+ * Defines the lifecycle scope for beans in the configuration.
+ */
 public enum Scope {
+    /** Single shared instance per context */
     SINGLETON,
+    /** New instance created for each request */
     PROTOTYPE
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f94438c and cf7eb0d.

📒 Files selected for processing (3)
  • annot/src/main/java/com/predic8/membrane/annot/generator/BeanClassGenerator.java (1 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/generator/Scope.java (1 hunks)
  • annot/src/main/java/com/predic8/membrane/annot/model/OtherAttributesInfo.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • annot/src/main/java/com/predic8/membrane/annot/generator/BeanClassGenerator.java
  • annot/src/main/java/com/predic8/membrane/annot/model/OtherAttributesInfo.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: predic8
Repo: membrane/api-gateway PR: 2240
File: annot/src/main/java/com/predic8/membrane/annot/generator/JsonSchemaGenerator.java:210-215
Timestamp: 2025-10-23T13:04:11.388Z
Learning: In JsonSchemaGenerator.java, when allowForeign is true for non-list child elements, use a property named "ref" (not "$ref") with type "string" to provide IDE completion hints for Spring bean references while avoiding JSON Schema keyword collision.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Automated tests
  • GitHub Check: Analyze (java)

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.

3 participants