Skip to content

Commit

Permalink
Maven plugin for Helidon Service Inject (#9461)
Browse files Browse the repository at this point in the history
Merged Core Registry and Inject registry:
- Single module with minimal dependencies 
- New core module to be used everywhere
- Replaces ServiceLoader

Introduce Helidon Service Maven Plugin:
- generates application binding class
- generates main class
- updated tests
  • Loading branch information
tomas-langer authored Dec 31, 2024
1 parent 51d07df commit 114afc4
Show file tree
Hide file tree
Showing 453 changed files with 14,082 additions and 10,314 deletions.
12 changes: 2 additions & 10 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1133,16 +1133,8 @@
<artifactId>helidon-service-codegen</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-codegen</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject</artifactId>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-maven-plugin</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.metadata</groupId>
Expand Down
5 changes: 5 additions & 0 deletions applications/parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-project</artifactId>
<version>${helidon.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
Expand Down
14 changes: 2 additions & 12 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1502,18 +1502,8 @@
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-codegen</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject-api</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.service.inject</groupId>
<artifactId>helidon-service-inject</artifactId>
<groupId>io.helidon.service</groupId>
<artifactId>helidon-service-maven-plugin</artifactId>
<version>${helidon.version}</version>
</dependency>

Expand Down
5 changes: 0 additions & 5 deletions builder/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common-config</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

</project>
26 changes: 25 additions & 1 deletion builder/api/src/main/java/io/helidon/builder/api/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private Option() {
}

/**
* Mark a prototype option as one that can be read from {@link io.helidon.common.config.Config}.
* Mark a prototype option as one that can be read from {@code io.helidon.common.config.Config}.
*/
@Target(ElementType.METHOD)
@Inherited
Expand Down Expand Up @@ -152,6 +152,30 @@ private Option() {
boolean discoverServices() default true;
}

/**
* Options annotated with this annotation will use service registry to discover instances to use.
* This annotation cannot be combined with {@link io.helidon.builder.api.Option.Configured} - if you want
* providers configured from configuration, kindly use {@link io.helidon.builder.api.Option.Provider}.
* <p>
* Behavior depends on the return type of the annotated method:
* <ul>
* <li>A single instance - if the instance is configured on the builder by hand, registry is not used</li>
* <li>An {@link java.util.Optional} instance - ditto</li>
* <li>A {@link java.util.List} of instances - instances configured on the builder are combined with instances
* discovered in the registry; there is a generated method that allows for disabling registry use for each
* service</li>
* </ul>
*
* Options annotated with this annotation will load the instances as the default value (before method {@code builder()})
* returns, thus you have full control over the field, be it an Optional, single value, or a List.
* <p>
* To support usage of custom service registry, a {@code builder(ServiceRegistry)} method will be generated as well.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface RegistryService {
}

/**
* Allowed values for this option.
* The allowed value is always configured as a string, and is compared to {@link java.lang.String#valueOf(Object)} of the
Expand Down
105 changes: 1 addition & 104 deletions builder/api/src/main/java/io/helidon/builder/api/Prototype.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Optional;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.config.Config;
import io.helidon.common.config.ConfiguredProvider;
import io.helidon.common.config.NamedService;

/**
* Prototype is generated from a prototype blueprint, and it is expected to be part of the public API of the module.
Expand Down Expand Up @@ -72,102 +65,6 @@ default BUILDER self() {
}
}

/**
* Extension of {@link io.helidon.builder.api.Prototype.Builder} that supports configuration.
* If a blueprint is marked as {@code @Configured}, build will accept configuration.
*
* @param <BUILDER> type of the builder
* @param <PROTOTYPE> type of the prototype to be built
*/
public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER, PROTOTYPE> {
/**
* Update builder from configuration.
* Any configured option that is defined on this prototype will be checked in configuration, and if it exists,
* it will override current value for that option on this builder.
* Options that do not exist in the provided config will not impact current values.
* The config instance is kept and may be used in builder decorator, it is not available in prototype implementation.
*
* @param config configuration to use
* @return updated builder instance
*/
BUILDER config(Config config);

/**
* Discover services from configuration.
* If already configured instances already contain a service of the same type and name that would be added from
* configuration, the configuration would be ignored (e.g. the user must make a choice whether to configure, or
* set using an API).
*
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service)
* @param serviceLoader helidon service loader for the expected type
* @param providerType type of the service provider interface
* @param configType type of the configured service
* @param allFromServiceLoader whether all services from service loader should be used, or only the ones with configured
* node
* @param existingInstances already configured instances
* @param <S> type of the expected service
* @param <T> type of the configured service provider that creates instances of S
* @return list of discovered services, ordered by {@link io.helidon.common.Weight} (highest weight is first in the list)
*/
default <S extends NamedService, T extends ConfiguredProvider<S>> List<S>
discoverServices(Config config,
String configKey,
HelidonServiceLoader<T> serviceLoader,
Class<T> providerType,
Class<S> configType,
boolean allFromServiceLoader,
List<S> existingInstances) {
return ProvidedUtil.discoverServices(config,
configKey,
serviceLoader,
providerType,
configType,
allFromServiceLoader,
existingInstances);
}

/**
* Discover service from configuration. If an instance is already configured using a builder, it will not be
* discovered from configuration (e.g. the user must make a choice whether to configure, or set using API).
*
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service - this method requires
* * zero to one configured services)
* @param serviceLoader helidon service loader for the expected type
* @param providerType type of the service provider interface
* @param configType type of the configured service
* @param allFromServiceLoader whether all services from service loader should be used, or only the ones with configured
* node
* @param existingValue value already configured, if the name is same as discovered from configuration
* @param <S> type of the expected service
* @param <T> type of the configured service provider that creates instances of S
* @return the first service (ordered by {@link io.helidon.common.Weight} that is discovered, or empty optional if none
* is found
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default <S extends NamedService, T extends ConfiguredProvider<S>> Optional<S>
discoverService(Config config,
String configKey,
HelidonServiceLoader<T> serviceLoader,
Class<T> providerType,
Class<S> configType,
boolean allFromServiceLoader,
Optional<S> existingValue) {
return ProvidedUtil.discoverService(config,
configKey,
serviceLoader,
providerType,
configType,
allFromServiceLoader,
existingValue);
}


}

/**
* A prototype {@link Prototype.Blueprint} may extend this interface
* to explicitly reference the associated runtime type.
Expand Down Expand Up @@ -262,7 +159,7 @@ public interface Factory<T> {

/**
* A blueprint annotated with this annotation will create a prototype that can be created from a
* {@link io.helidon.common.config.Config} instance. The builder will also have a method {@code config(Config)} that
* {@code io.helidon.common.config.Config} instance. The builder will also have a method {@code config(Config)} that
* reads all options annotated with {@link io.helidon.builder.api.Option.Configured} from the config.
*/
@Target(ElementType.TYPE)
Expand Down
5 changes: 1 addition & 4 deletions builder/api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,8 +21,5 @@

requires transitive io.helidon.common;

requires static io.helidon.common.config;

exports io.helidon.builder.api;

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import static io.helidon.builder.codegen.Types.OPTION_DEFAULT_METHOD;
import static io.helidon.builder.codegen.Types.OPTION_PROVIDER;
import static io.helidon.builder.codegen.Types.OPTION_REDUNDANT;
import static io.helidon.builder.codegen.Types.OPTION_REGISTRY_SERVICE;
import static io.helidon.builder.codegen.Types.OPTION_REQUIRED;
import static io.helidon.builder.codegen.Types.OPTION_SAME_GENERIC;
import static io.helidon.builder.codegen.Types.OPTION_SINGULAR;
Expand All @@ -60,6 +61,7 @@ record AnnotationDataOption(Javadoc javadoc,
AccessModifier accessModifier,
boolean required,
boolean validateNotNull,
boolean registryService,
boolean provider,
TypeName providerType,
boolean providerDiscoverServices,
Expand Down Expand Up @@ -101,6 +103,7 @@ static AnnotationDataOption create(TypeHandler handler, TypedElementInfo element
.orElse(false);
}
accessModifier = accessModifier(element);
boolean registryService = element.hasAnnotation(OPTION_REGISTRY_SERVICE);
if (element.hasAnnotation(OPTION_PROVIDER)) {
Annotation annotation = element.annotation(OPTION_PROVIDER);
providerBased = true;
Expand Down Expand Up @@ -171,6 +174,7 @@ static AnnotationDataOption create(TypeHandler handler, TypedElementInfo element
accessModifier,
required,
validateNotNull,
registryService,
providerBased,
providerType,
discoverServices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ private static void addBuilderMethod(ClassModel.Builder classModel,
TypeName builderTypeName,
List<TypeArgument> typeArguments,
String ifaceName) {

classModel.addMethod(builder -> {
builder.isStatic(true)
.name("builder")
Expand Down Expand Up @@ -438,7 +439,7 @@ private void process(RoundContext roundContext, TypeInfo blueprint) {
blueprint.typeName(),
blueprint.originatingElementValue());

if (typeContext.typeInfo().supportsServiceRegistry() && typeContext.propertyData().hasProvider()) {
if (typeContext.typeInfo().supportsServiceRegistry()) {
for (PrototypeProperty property : typeContext.propertyData().properties()) {
if (property.configuredOption().provider()) {
this.serviceLoaderContracts.add(property.configuredOption().providerType().genericTypeName().fqName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -129,7 +130,8 @@ private static GeneratedMethod prototypeMethod(Errors.Collector errors,
customMethod.returnType(),
generatedArgs,
// todo the javadoc may differ (such as when we have an additional parameter for instance methods)
customMethod.javadoc()),
customMethod.javadoc(),
customMethod.typeParameters()),
annotations,
codeGenerator);
}
Expand Down Expand Up @@ -178,7 +180,8 @@ private static GeneratedMethod builderMethod(Errors.Collector errors,
customMethod.name(),
typeInformation.prototypeBuilder(),
generatedArgs,
customMethod.javadoc()),
customMethod.javadoc(),
customMethod.typeParameters),
annotations,
codeGenerator);
}
Expand Down Expand Up @@ -227,7 +230,8 @@ private static GeneratedMethod factoryMethod(Errors.Collector errors,
customMethod.name(),
customMethod.returnType(),
customMethod.arguments(),
customMethod.javadoc()),
customMethod.javadoc(),
customMethod.typeParameters()),
annotations,
codeGenerator);
}
Expand Down Expand Up @@ -314,7 +318,26 @@ private static List<CustomMethod> findMethods(TypeContext.TypeInformation typeIn
.filter(Predicate.not(String::isBlank)) // we do not care about blank values
.toList();

Method customMethod = new Method(customMethodsType.typeName(), methodName, returnType, arguments, javadoc);
List<TypeName> typeNames = it.typeParameters();
List<TypeName> typeParametersToUse = new ArrayList<>();
if (!typeNames.isEmpty()) {
// look for type parameters that differ in name from the ones defined by the blueprint
Set<String> usedNames = typeInformation.blueprintType().declaredType()
.typeArguments()
.stream()
.map(TypeName::className)
.collect(Collectors.toSet());
typeNames.stream()
.filter(methodTypeParam -> !usedNames.contains(methodTypeParam.className()))
.forEach(typeParametersToUse::add);
}

Method customMethod = new Method(customMethodsType.typeName(),
methodName,
returnType,
arguments,
javadoc,
typeParametersToUse);

return new CustomMethod(customMethod,
methodProcessor.process(errors,
Expand Down Expand Up @@ -343,7 +366,8 @@ record Method(TypeName declaringType,
String name,
TypeName returnType,
List<Argument> arguments,
List<String> javadoc) {
List<String> javadoc,
List<TypeName> typeParameters) {

}

Expand Down
Loading

0 comments on commit 114afc4

Please sign in to comment.