There are two use cases we cover:
- We need a type with a builder (we will use
Keys
as an example) - We need a runtime object, with a prototype with a builder (we will use
Retry
as an example)
For both use cases, we need to understand how to create instances, obtain builders etc.
For this simple approach, the user facing API will look as it does now:
Keys keys=Keys.builder()
.name("name")
.build();
The "blueprint" of such type:
import io.helidon.config.metadata.Configured;
import io.helidon.config.metadata.ConfiguredOption;
@Prototype.Blueprint
@Configured // support method config(Config) on the builder, and a static create(Config)
interface KeysBlueprint{
@ConfiguredOption(required = true)
String name();
}
This will generate:
Keys extends KeysBlueprint
interfaceKeys.BuilderBase implements Keys
base builder, to support extensibility ofKeys
Keys.Builder extends Keys.BuilderBase, io.helidon.common.Builder<Builder, Keys>
inner class - the fluent API builder forKeys
Keys.BuilderBase.KeysImpl implements Keys
implementation ofKeys
For this approach, the user facing API will be similar to what we do now:
Retry retry=Retry.builder() // method builder is not generated, must be hand coded, and will return "RetryPrototype.builder()"
.build(); // generated, creates a Retry instance through a factory method defined on Retry or on RetryPrototypeBlueprint
RetryPrototype prototype=RetryPrototype.builder()
.buildPrototype(); // alternative build method to obtain the intermediate prototype object
Retry retryFromSetup=prototype.build(); // to runtime type
The "blueprint" of such type:
@Prototype.Blueprint
@Configured // support method config(Config) on the builder, and a static create(Config) method if desired
intrerface RetryPrototypeBlueprint extends Prototype.Factory<Retry> {
@ConfiguredOption(required = true)
String name();
}
Annotations:
Annotation | Required | Description |
---|---|---|
Prototype.Blueprint |
true |
Annotation on the blueprint interface is required to trigger annotation processing |
Prototype.Implement |
false |
Add additional implemented types to the generated prototype |
Prototype.Annotated |
false |
Allows adding an annotation (or annotations) to the generated class or methods |
Prototype.FactoryMethod |
false |
Use in generated code to mark static factory methods, also can be used on blueprint factory methods to be used during code generation, and on custom methods to mark static methods to be added to prototype |
Prototype.Singular |
false |
Used for lists, sets, and maps to add methods add* /put* in addition to the full collection setters |
Prototype.SameGeneric |
false |
Use for maps, where we want a setter method to use the same generic type for key and for value (such as Class<T> key, T valuel ) |
Prototype.Redundant |
false |
A redundant option will not be part of generated toString , hashCode , and equals methods (allows finer grained control) |
Prototype.Confidential |
false |
A confidential option will not have value visible when toString is called, only if it is null or it has a value (**** ) |
Prototype.CustomMethods |
false |
reference a class that will contain declarations (all static) of custom methods to be added to the generated code, can add prototype, builder, and factory methods |
Prototype.BuilderMethod |
false |
Annotation to be placed on factory methods that are to be added to builder, first parameter is the BuilderBase<?, ?> of the prototype |
Prototype.PrototypeMethod |
false |
Annotation to be placed on factory methods that are to be added to prototype, first parameter is the prototype instance |
RuntimeType.PrototypedBy |
true |
Annotation on runtime type that is created from a Prototype , to map it to the prototype it can be created from, used to trigger annotation processor for validation |
Interfaces:
Interface | Generated | Description |
---|---|---|
RuntimeType.Api |
false |
runtime type must implement this interface to mark which prototype is used to create it |
Prototype.Factory |
false |
if blueprint implements factory, it means the prototype is used to create a single runtime type and will have methods build and get both on builder an on prototype interface that create a new instance of the runtime object |
Prototype.BuilderDecorator |
false |
custom decorator to modify builder before validation is done in method build |
Prototype.Api |
true |
all prototypes implement this interface |
Prototype.Builder |
true |
all prototype builders implement this interface, defines method buildPrototype |
Prototype.ConfiguredBuilder |
true |
all prototype builders that support configuration implement this interface, defines method config(Config) |
We can define a configured option as follows:
@ConfiguredOption(key = "security-providers", provider = true, providerType = SecurityProviderProvider.class, providerDiscoverServices = false)
Rules:
providerType
MUST extendio.helidon.common.config.ConfiguredProvider
- The method MUST return a
List
of the type the provider creates, so in this case we consider theSecurityProviderProvider
to be capable of creating aSecurityProvider
instance from configuration, so the return type would beList<SecurityProvider>
, whereSecurityProvider extends NamedService
and
SecurityProviderProvider extends ConfiguredProvider<SecurityProvider>
This will expect the following configuration:
security-providers:
discover-services: true # optional, to override "providerDiscoverServices" option
providers:
- name: "my-provider"
type: "http-basic"
enabled: true
The generated code will read all nodes under providers
and map them to an instance.
Part of the naming rules is constant, part depends on whether we use two or three types, as mentioned above.
Blueprint MUST be package local, and MUST be named with a Blueprint
suffix. The part of the name before the suffix will be the prototype name.
For cases, where the Prototype
is the target desired type (such as TypeName
, Keys
), the prototype name is a name of the type we represent (no suffixes, prefixes etc.).
Example: TypeName
would have the following structure (as can be seen in the builder/tests/common-types):
TypeNameBlueprint
- the definition of the typeTypeName
- the generated type to be used as an API
For cases, where the Prototype
serves as a configuration object of a runtime type (such as WebServerConfig
, RetryConfig
),
the prototype name should have a Config
suffix, and the runtime type is a name of the type we represent.
Example: Retry
would have the following structure (can be seen in Fault Tolerance):
RetryConfigBlueprint
- the definition of the configRetryConfig
- the prototypeRetry
- the runtime type