Skip to content

Commit

Permalink
Merge branch 'main' into issue8857
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
  • Loading branch information
jbescos committed Dec 10, 2024
2 parents 8649cee + 4591886 commit e6b3a66
Show file tree
Hide file tree
Showing 196 changed files with 11,996 additions and 2,255 deletions.
4 changes: 4 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,10 @@
<groupId>io.helidon.microprofile.rest-client</groupId>
<artifactId>helidon-microprofile-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.microprofile.rest-client-metrics</groupId>
<artifactId>helidon-microprofile-rest-client-metrics</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.microprofile.reactive-streams</groupId>
<artifactId>helidon-microprofile-reactive-streams</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion applications/mp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${version.plugin.hibernate-enhance}</version>
<!-- Force upgrade for Java 22 support. Should remove after Hibernate upgrade -->
<!-- Force upgrade for latest Java support. -->
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ For details on an Oracle Docker image, see https://github.com/oracle/docker-imag
</map>
<map>
<value key="groupId">com.oracle.database.jdbc</value>
<value key="artifactId">ucp</value>
<value key="artifactId">ucp11</value>
<value key="scope">runtime</value>
</map>
</list>
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,11 @@
<artifactId>helidon-microprofile-rest-client</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.microprofile.rest-client-metrics</groupId>
<artifactId>helidon-microprofile-rest-client-metrics</artifactId>
<version>${helidon.version}</version>
</dependency>

<!-- Reactive Streams Operators -->
<dependency>
Expand Down
290 changes: 242 additions & 48 deletions builder/README.md

Large diffs are not rendered by default.

149 changes: 2 additions & 147 deletions builder/api/README.md
Original file line number Diff line number Diff line change
@@ -1,148 +1,3 @@
# Builder
# Builder API

## Description

There are two use cases we cover:

1. We need a type with a builder (we will use `Keys` as an example)
2. 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.

### Type with a builder

For this simple approach, the user facing API will look as it does now:

```java
Keys keys=Keys.builder()
.name("name")
.build();
```

The "blueprint" of such type:

```java
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` interface
- `Keys.BuilderBase implements Keys` base builder, to support extensibility of `Keys`
- `Keys.Builder extends Keys.BuilderBase, io.helidon.common.Builder<Builder, Keys>` inner class - the fluent API builder
for `Keys`
- `Keys.BuilderBase.KeysImpl implements Keys` implementation of `Keys`

### Runtime object, blueprint, builder

For this approach, the user facing API will be similar to what we do now:

```java
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:

```java
@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();
}
```

## Types, interfaces

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

## Configured providers

We can define a configured option as follows:
`@ConfiguredOption(key = "security-providers", provider = true, providerType = SecurityProviderProvider.class, providerDiscoverServices = false)`

Rules:

1. `providerType` MUST extend `io.helidon.common.config.ConfiguredProvider`
2. The method MUST return a `List` of the type the provider creates, so in this case we consider the `SecurityProviderProvider`
to be capable of creating a `SecurityProvider` instance from configuration, so the return type would
be `List<SecurityProvider>`, where `SecurityProvider extends NamedService` and
`SecurityProviderProvider extends ConfiguredProvider<SecurityProvider>`

This will expect the following configuration:

```yaml
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.

## Naming rules

Part of the naming rules is constant, part depends on whether we use two or three types, as mentioned above.

### Blueprint name
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.

### Blueprint -> Prototype
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](../tests/common-types)):

- `TypeNameBlueprint` - the definition of the type
- `TypeName` - the generated type to be used as an API

### Blueprint -> Prototype -> Runtime type
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 config
- `RetryConfig` - the prototype
- `Retry` - the runtime type
This document is merged into parent readme, see [Builder](../README.md).
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ private Option() {
* This is useful for example when setting a compound option, where we need to set additional options on this builder.
* <p>
* Decorator on collection based options will be ignored.
* Decorator on optional values must accept an option (as it would be called both from the setter and unset methods).
* Decorator on optional values must accept an optional (as it would be called both from the setter and unset methods).
*/
@Target(ElementType.METHOD)
// note: class retention needed for cases when derived builders are inherited across modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.helidon.codegen.classmodel.InnerClass;
import io.helidon.codegen.classmodel.Javadoc;
import io.helidon.codegen.classmodel.Method;
import io.helidon.common.Size;
import io.helidon.common.types.AccessModifier;
import io.helidon.common.types.TypeName;
import io.helidon.common.types.TypeNames;
Expand Down Expand Up @@ -220,8 +221,14 @@ Consumer<ContentBuilder<?>> toDefaultValue(String defaultValue) {
.addContent(defaultValue)
.addContent("\"");
}
if (TypeNames.SIZE.equals(typeName)) {
CodegenValidator.validateSize(enclosingType, annotatedMethod, OPTION_DEFAULT, "value", defaultValue);
return content -> content.addContent(Size.class)
.addContent(".parse(\"")
.addContent(defaultValue)
.addContent("\")");
}
if (TypeNames.DURATION.equals(typeName)) {

CodegenValidator.validateDuration(enclosingType, annotatedMethod, OPTION_DEFAULT, "value", defaultValue);
return content -> content.addContent(Duration.class)
.addContent(".parse(\"")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.URI;
import java.time.Duration;

import io.helidon.common.Size;
import io.helidon.common.types.TypeName;
import io.helidon.common.types.TypedElementInfo;

Expand Down Expand Up @@ -86,4 +87,35 @@ public static String validateDuration(TypeName enclosingType,
element.originatingElementValue());
}
}

/**
* Validate a {@link io.helidon.common.Size} annotation on a method, field, or constructor.
*
* @param enclosingType type that owns the element
* @param element annotated element
* @param annotationType type of annotation
* @param property property of annotation
* @param value actual value read from the annotation property
* @return the value
* @throws io.helidon.codegen.CodegenException with correct source element describing the problem
*/
public static String validateSize(TypeName enclosingType,
TypedElementInfo element,
TypeName annotationType,
String property,
String value) {
try {
Size.parse(value);
return value;
} catch (Exception e) {
throw new CodegenException("Size expression of annotation " + annotationType.fqName() + "."
+ property + "(): "
+ "\"" + value + "\" cannot be parsed. Size expects an"
+ " expression such as '120 KB' (120 * 1024 * 1024), "
+ "'120 kB' (120 * 1000 * 1000), or '120 KiB' (same as KB)"
+ " Please check javadoc of " + Size.class.getName() + " class.",
e,
element.originatingElementValue());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public abstract class TypeInfoFactoryBase {
TypeName.create(Target.class),
TypeName.create(Retention.class),
TypeName.create(Repeatable.class));
private static final Set<String> ACCESS_MODIFIERS = Set.of("PUBLIC", "PRIVATE", "PROTECTED");

/**
* There are no side effects of this constructor.
Expand Down Expand Up @@ -144,10 +145,15 @@ protected static Set<io.helidon.common.types.Modifier> modifiers(CodegenContext
Set<io.helidon.common.types.Modifier> result = new HashSet<>();

for (String stringModifier : stringModifiers) {
String upperCased = stringModifier.toUpperCase(Locale.ROOT);
if (ACCESS_MODIFIERS.contains(upperCased)) {
// ignore access modifiers, as they are handled elsewhere
continue;
}
try {
result.add(io.helidon.common.types.Modifier.valueOf(stringModifier.toUpperCase(Locale.ROOT)));
result.add(io.helidon.common.types.Modifier.valueOf(upperCased));
} catch (Exception ignored) {
// we do not care about modifiers we do not understand - either access modifier, or something new
// we do not care about modifiers we do not understand
ctx.logger().log(System.Logger.Level.TRACE,
"Modifier " + stringModifier + " not understood by type info factory.");
}
Expand Down
Loading

0 comments on commit e6b3a66

Please sign in to comment.