|
| 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