Skip to content

Commit fbb440d

Browse files
lavocattcursoragent
andcommitted
ARTEMIS-5372: Add JSON Schema generator module (artemis-jsonschema)
Generates a JSON Schema (Draft 7) describing all valid broker.properties configuration options. Built from multiple sources: Java reflection on ConfigurationImpl, XSD constraints, JavaDoc, source constants, and factory parameter discovery. Two-phase IR architecture: build graph via reflection, enrich with metadata from extractors, emit schema with $ref extraction. Supports factory-based polymorphism (Netty/InVM oneOf variants), class-based polymorphism (AMQP connection element subtypes), and hot-reloadable property flagging. Externalized configuration in META-INF/schema-generator-config.json. Add optional JSON Schema validation for broker configuration files. When the artemis-jsonschema JAR is on the classpath and validation is enabled via -Dartemis.config.validate-json=true, JSON broker configs are validated against the generated schema before being applied. JsonSchemaValidator loads the schema from the classpath resource packaged by artemis-jsonschema. Invalid configurations are rejected with detailed error messages. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fde82ef commit fbb440d

60 files changed

Lines changed: 9094 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.project

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,15 @@
1414
<natures>
1515
<nature>org.eclipse.m2e.core.maven2Nature</nature>
1616
</natures>
17+
<filteredResources>
18+
<filter>
19+
<id>1780036432626</id>
20+
<name></name>
21+
<type>30</type>
22+
<matcher>
23+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
24+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
25+
</matcher>
26+
</filter>
27+
</filteredResources>
1728
</projectDescription>

artemis-jsonschema/README.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Artemis JSON Schema Generator
2+
3+
Generates JSON Schema (Draft 7) for Apache Artemis broker configuration validation.
4+
5+
## Quick Start
6+
7+
```bash
8+
# Generate schema (requires artemis-server to be built first)
9+
cd artemis-jsonschema
10+
mvn process-classes -Pgenerate-schema -Dgenerate-schema -DskipTests
11+
12+
# Output at:
13+
# target/schema/org.apache.artemis/jsonschema/broker-config-schema.json
14+
```
15+
16+
## Architecture
17+
18+
```mermaid
19+
flowchart TD
20+
subgraph phase1 [1. IR Graph Generation]
21+
IRBuilder["IRBuilder
22+
reflects ConfigurationImpl"]
23+
IRBuilder --> IR["SchemaIR
24+
ClassNodes + PropertyNodes"]
25+
IRBuilder -->|nested types| Poly["PolymorphismResolver
26+
detects subclass hierarchies"]
27+
Poly --> IR
28+
end
29+
30+
subgraph phase2 [2. Factory Variant Discovery]
31+
FD["FactoryDiscovery
32+
classpath scan"] --> Registry["FactoryParameterRegistry"]
33+
Registry --> TFVB["TransportFactoryVariantBuilder
34+
Netty / InVM"]
35+
Registry --> LMVB["LoginModuleVariantBuilder
36+
LDAP / Properties / Guest / ..."]
37+
TFVB -->|"synthetic ClassNodes
38+
with oneOf"| IR
39+
LMVB -->|"synthetic ClassNodes
40+
with oneOf"| IR
41+
end
42+
43+
subgraph phase3 [3. Enrichment]
44+
JavaDoc["SetterGetterJavadocExtractor
45+
descriptions"]
46+
XSD["XsdExtractor
47+
descriptions, enums, constraints"]
48+
Meta["MetadataExtractor
49+
hot-reloadable flags"]
50+
TC["TypeConverterExtractor
51+
byte notation union types"]
52+
JavaDoc --> Enricher
53+
XSD --> Enricher
54+
Meta --> Enricher
55+
TC --> Enricher
56+
Enricher["Enricher
57+
extract + enrich"] -->|"PropertyDescriptors
58+
merged into IR"| IR
59+
end
60+
61+
subgraph phase4 [4. Schema Emission]
62+
IR --> Emitter["SchemaEmitter"]
63+
Emitter --> Strategies["4 PropertyEmitter strategies
64+
Primitive / NestedObject / Map / Collection"]
65+
Strategies --> JSONSchema["broker-config-schema.json"]
66+
end
67+
68+
phase1 --> phase2
69+
phase2 --> phase3
70+
phase3 --> phase4
71+
```
72+
73+
The pipeline is strictly linear: **IR -> Factories -> Enrichment -> Emission**.
74+
No cycles, no post-emission patching.
75+
76+
### Pipeline Phases
77+
78+
| Phase | Component | Input | Output |
79+
|-------|-----------|-------|--------|
80+
| 1 | `IRBuilder` | `ConfigurationImpl.class` | `SchemaIR` graph |
81+
| 2 | `FactoryVariantBuilder.createAll()` | Classpath + IR | Synthetic factory variant ClassNodes |
82+
| 3 | `Enricher` with 4 `Extractor` implementations | Source files, XSD | Enriched IR |
83+
| 4 | `SchemaEmitter` + 4 `PropertyEmitter` strategies | Enriched IR | JSON Schema |
84+
85+
### Enrichment Extractors
86+
87+
| Extractor | Parses | Contributes |
88+
|-----------|--------|-------------|
89+
| `SetterGetterJavadocExtractor` | Configuration interface JavaDoc | Descriptions for top-level properties |
90+
| `XsdExtractor` | `artemis-configuration.xsd` | Descriptions, enums, min/max constraints |
91+
| `MetadataExtractor` | `ConfigurationImpl` source | Hot-reload whitelist (`x-hot-reloadable`) |
92+
| `TypeConverterExtractor` | `ConfigurationImpl` converter registrations | Union types `["integer", "string"]` for byte-notation fields, `x-converter`, `pattern` |
93+
94+
### Factory Variant Builders
95+
96+
Artemis uses a map-of-strings pattern for plugin extensibility: a configuration class
97+
holds a discriminator field selecting the implementation, and an opaque `Map<String, Object> params`
98+
whose valid keys depend on which implementation is selected. There is no
99+
`NettyAcceptorFactoryConfiguration.class` with typed bean properties for `host`, `port`,
100+
`sslEnabled` -- those are convention-based string keys discovered from `*_PROP_NAME` constants
101+
and `ConfigKey` enums.
102+
103+
The variant builders create synthetic `$defs` that reconstruct the type information from
104+
convention rather than from the type system.
105+
106+
| Builder | Target Class | Discriminator | Params |
107+
|---------|-------------|---------------|--------|
108+
| `TransportFactoryVariantBuilder` | `TransportConfiguration` | `factoryClassName` | host, port, ssl... |
109+
| `LoginModuleVariantBuilder` | `JaasAppConfigurationEntry` | `loginModuleClass` | user, role, connectionUrl... |
110+
111+
Each variant uses `oneOf` with `required` on the discriminator and `const` + `default` for identity.
112+
113+
### Package Structure
114+
115+
```
116+
jsonschema/
117+
Pipeline.java -- orchestrator, the only top-level class
118+
config/ -- SchemaGeneratorConfig
119+
enrichment/ -- Enricher, Extractor interface, 4 extractors
120+
factories/ -- FactoryDiscovery, FactoryParameterRegistry, FactoryVariantBuilder + 2 subclasses
121+
ir/ -- SchemaIR, IRBuilder, SchemaEmitter, SchemaType, PropertyDescriptor, PropertyMetadata, ...
122+
emitters/ -- PropertyEmitter + 4 strategies (Primitive, NestedObject, Map, Collection)
123+
annotation/ -- @ConfigProperty, @Heuristic
124+
validation/ -- SchemaValidator
125+
```
126+
127+
### Type System
128+
129+
`SchemaType` is a value type wrapping `List<SchemaType.Kind>` where `Kind` is an enum
130+
(`STRING`, `INTEGER`, `NUMBER`, `BOOLEAN`, `OBJECT`, `ARRAY`). Single-element for simple
131+
types, multi-element for unions like `["integer", "string"]` (byte-notation fields).
132+
Emitted as a bare string or list depending on cardinality. All type assignments in the
133+
codebase go through `SchemaType.Kind` -- the compiler enforces valid types.
134+
135+
## Design Decisions
136+
137+
### No default values in the schema
138+
139+
Default values are NOT extracted from code. There are three layers of defaults in Artemis
140+
(Java field initializers, XML parser overrides, `artemis create` template values) and no
141+
single source of truth that code inspection can capture. Defaults should be obtained by
142+
running `artemis create` + `artemis properties` against a real broker instance.
143+
144+
The only exceptions are `factoryClassName` / `loginModuleClass` on factory variants, where
145+
the default matches the `const` discriminator -- that's identity, not a runtime default.
146+
147+
### XSD does not contribute types
148+
149+
The XSD declares types for XML configuration (`xsd:string` for fields accepting byte notation
150+
like `"10M"`). But broker.properties uses Java types -- `int` fields only accept integers,
151+
`long` fields accept byte notation through a registered converter. The reflection type is
152+
the truth for broker.properties; the XSD type is the truth for XML configs. The schema
153+
generator targets broker.properties, so reflection wins.
154+
155+
### Factory discriminator as required + const + default
156+
157+
Factory variant `$defs` set `required: ["factoryClassName"]` (or `loginModuleClass`) so that
158+
JSON Schema `oneOf` correctly discriminates between variants. Without `required`, a JSON
159+
that omits the discriminator matches all variants. The `default` matches the `const` to
160+
document the expected value.
161+
162+
## Configuration
163+
164+
`src/main/resources/META-INF/schema-generator-config.json`:
165+
166+
- `factoryInterfaces`: interfaces scanned for implementations (AcceptorFactory, ConnectorFactory, LoginModule)
167+
- `factoryScanPackages`: classpath packages scanned by Reflections
168+
- `ignoredProperties`: property names excluded from IR traversal (avoid circular references)
169+
- `xsdComplexTypeToPathPattern`: maps XSD complexType names to broker.properties path prefixes
170+
171+
## Extension Guide
172+
173+
### Adding a new broker configuration property
174+
175+
Nothing to do. Reflection auto-discovers it from `ConfigurationImpl`.
176+
177+
### Adding documentation for a property
178+
179+
Option A (recommended): Add JavaDoc to the setter or getter in the Configuration class.
180+
181+
Option B (explicit): Add `@ConfigProperty(description = "...")` to the method.
182+
183+
### Adding a new factory-polymorphic type
184+
185+
1. Create a new `FactoryVariantBuilder` subclass in the `factories` package
186+
2. Define `getTargetClassName()`, `getDiscriminatorField()`, `getParamsField()`, `filterFactories()`
187+
3. Add it to `FactoryVariantBuilder.createAll()`
188+
189+
### Adding a new enrichment source
190+
191+
1. Create a class implementing `Extractor`
192+
2. Implement `extract(Path artemisRoot)` returning `List<PropertyDescriptor>`
193+
3. Add it to the extractor list in `Pipeline.run()`
194+
195+
## Testing
196+
197+
```bash
198+
# All tests (unit + integration)
199+
mvn test -pl artemis-jsonschema
200+
201+
# Unit tests only
202+
mvn test -pl artemis-jsonschema -Dtest='!*IntegrationTest'
203+
```
204+
205+
## License
206+
207+
Apache License 2.0 -- See LICENSE file in repository root.

0 commit comments

Comments
 (0)