Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maven plugin for Helidon Service Inject #9461

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c49ce14
Small fixes and pre-requisite changes.
tomas-langer Nov 4, 2024
43301ef
Support for injection Main class
tomas-langer Nov 4, 2024
c192431
Maven plugin for Helidon Service Inject
tomas-langer Nov 4, 2024
24dd507
Tests to validate previous commits.
tomas-langer Nov 4, 2024
47c482a
Review fixes.
tomas-langer Nov 26, 2024
50e0681
Rebased on main and updated error message for record field injection.
tomas-langer Nov 27, 2024
3d83e49
Review comments.
tomas-langer Nov 27, 2024
11bb3ca
Maven plugin integration tests.
tomas-langer Nov 27, 2024
e766a9e
Use `release` if available for codegen compiler
tomas-langer Nov 28, 2024
d4a1e63
Fix qualifiers
tomas-langer Dec 2, 2024
d77615b
Updates to registry (less dependencies, single module)
tomas-langer Dec 5, 2024
7497e67
Single registry.
tomas-langer Dec 10, 2024
ea8924a
Fixed spotbugs, checkstyle etc.
tomas-langer Dec 10, 2024
f5720f3
Copyright fixes.
tomas-langer Dec 10, 2024
28823e1
New main class generator, binding generator. All tests passing in ser…
tomas-langer Dec 16, 2024
cb2a49d
Fix config provider to honor default config sources when meta configu…
tomas-langer Dec 17, 2024
1b8ae0a
Updated docs.
tomas-langer Dec 17, 2024
adbce97
A small update to the generated code and registry, to register shutdo…
tomas-langer Dec 17, 2024
5d074ac
Update documentation
tomas-langer Dec 17, 2024
bf94683
Fix versions of plugins and release of java in maven invoker tests
tomas-langer Dec 17, 2024
990ba2c
Ensure bindings must not fail if no descriptor discovered, as there m…
tomas-langer Dec 17, 2024
ef78142
Fixed service supply. Now we try to use all matchin service descripto…
tomas-langer Dec 17, 2024
99dd774
Plugin management for services Maven plugin
tomas-langer Dec 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 2 additions & 10 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1129,16 +1129,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 @@ -1492,18 +1492,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