From a079f4941d35fe12fccd3590a106df0f0a990e26 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 27 Nov 2025 18:41:23 +0100 Subject: [PATCH 1/2] Documentation of service registry services. Fixes to existing modules (duplication of service). --- .../io/helidon/common/mapper/MappersImpl.java | 3 +- .../service-registry/service_reference.adoc | 125 +++++--- .../helidon/service/codegen/CoreService.java | 278 ------------------ 3 files changed, 93 insertions(+), 313 deletions(-) delete mode 100644 service/codegen/src/main/java/io/helidon/service/codegen/CoreService.java diff --git a/common/mapper/src/main/java/io/helidon/common/mapper/MappersImpl.java b/common/mapper/src/main/java/io/helidon/common/mapper/MappersImpl.java index d4ae35c37c7..bf8d2d3d507 100644 --- a/common/mapper/src/main/java/io/helidon/common/mapper/MappersImpl.java +++ b/common/mapper/src/main/java/io/helidon/common/mapper/MappersImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. + * Copyright (c) 2019, 2025 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. @@ -33,7 +33,6 @@ * Implementation of {@link io.helidon.common.mapper.Mappers}. */ @SuppressWarnings("removal") -@Service.Singleton final class MappersImpl implements MapperManager, Mappers { private final Map> classCache = new ConcurrentHashMap<>(); private final Map> typeCache = new ConcurrentHashMap<>(); diff --git a/docs/src/main/asciidoc/service-registry/service_reference.adoc b/docs/src/main/asciidoc/service-registry/service_reference.adoc index 24368497cd5..514e1a32159 100644 --- a/docs/src/main/asciidoc/service-registry/service_reference.adoc +++ b/docs/src/main/asciidoc/service-registry/service_reference.adoc @@ -22,54 +22,113 @@ ifndef::rootdir[:rootdir: {docdir}/..] = Service Registry Reference -The following section lists all services and modules that provide them, grouped by module. +The following section lists all services and modules that provide them. Note: this is a work in progress, not listing the full set of contracts yet! -- <> -- <> -- <> +== Service registry contracts -== Module `io.helidon.common.config` [[helidon-common-config]] +[cols="2,1,2,6,5"] |=== -|Contract |Weight |Name qualifier |Description - -|`io.helidon.common.config.Config` +|Contract (package, class) |Weight | Module | Description |Qualifiers +.2+|`io.helidon.common.config` +`Config` |`80` +|`io.helidon.common.config` +|Empty config instance |N/A -|Common config instance (empty) -|=== - -== Module `io.helidon.scheduling` [[helidon-scheduling]] - -|=== -|Contract |Weight |Name qualifier |Description - -|`io.helidon.scheduling.TaskManager` |`90` +|`io.helidon.config` +|Configuration either from meta configuration (config profiles), or from service registry |N/A -|Management of scheduled tasks -|=== - -== Module `io.helidon.validation` [[helidon-validation]] - -|=== -|Contract |Weight |Name qualifier |Description - -|`java.time.Clock` +|`io.helidon.config` +`Config` |`90` +|`io.helidon.config` +|Configuration either from meta configuration (config profiles), or from service registry |N/A -|Clock used to check calendar related constraints, defaults to current time-zone - -|`io.helidon.validation.TypeValidation` +|`io.helidon.scheduling` +`TaskManager` |`90` +|`io.helidon.scheduling` +|Management of scheduled tasks +|N/A +|`java.time` +`Clock` +|`90` +|`io.helidon.validation` +|Clock used to check calendar related constraints, defaults to current time-zone |N/A +|`io.helidon.validation` +`TypeValidation` +|`90` +|`io.helidon.validation` |Methods to validate type annotated with `@Validation.Validated` - -|`io.helidon.validation.spi.ConstraintValidatorProvider` +|N/A +|`io.helidon.validation.spi` +`ConstraintValidatorProvider` |`70` -|Named by the constraint annotation type (for each built-in constraint) +|`io.helidon.validation` |Constraint validator providers for each built-in constraint - +|Named by the constraint annotation type (for each built-in constraint) +|`io.helidon.common.mapper` +`Mappers` +|`100` +|`io.helidon.common.mapper` +|Access to mappers, to map (convert) types +|N/A +|`io.helidon.common.mapper` +`MapperProvider` +|`0.1` +|`io.helidon.common.mapper` +|A provider of mappers +|N/A +|`io.helidon.common.mapper` +`DefaultResolver` +|`100` +|`io.helidon.common.mapper` +|Resolver of defaults annotation to a list of expected types +|N/A +|`*` +|N/A +|`io.helidon.config` +|Injection point of a configured object +|`@Configuration.Value` +|`io.helidon.config` +`MetaConfig` +|`100` +|`io.helidon.config` +|Config "meta-configuration" +|N/A +|`io.helidon.config` +`MetaConfig` +|`100` +|`io.helidon.config` +|Config source "meta-configuration" +|Named with a config type +|`io.helidon.webserver.http.spi` +`ErrorHandlerProvider` +|`100` +|N/A +|Error handler provider to add to WebServer +|N/A +|`io.helidon.webserver` +`WebServer` +|`100` +|`io.helidon.webserver` +|WebServer instance, only available in Helidon Declarative +|N/A +|`io.helidon.security` +`Security` +|`100` +|`io.helidon.security` +|Security +|N/A +|`io.helidon.health` +`HealthCheck` +|N/A +|N/A +|Health check instances to be added to WebServer health observer +|N/A |=== diff --git a/service/codegen/src/main/java/io/helidon/service/codegen/CoreService.java b/service/codegen/src/main/java/io/helidon/service/codegen/CoreService.java deleted file mode 100644 index cc615bf83d9..00000000000 --- a/service/codegen/src/main/java/io/helidon/service/codegen/CoreService.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.service.codegen; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import io.helidon.codegen.CodegenException; -import io.helidon.codegen.ElementInfoPredicates; -import io.helidon.common.types.AccessModifier; -import io.helidon.common.types.ElementKind; -import io.helidon.common.types.Modifier; -import io.helidon.common.types.ResolvedType; -import io.helidon.common.types.TypeInfo; -import io.helidon.common.types.TypeName; -import io.helidon.common.types.TypeNames; -import io.helidon.common.types.TypedElementInfo; - -import static io.helidon.service.codegen.ServiceCodegenTypes.SERVICE_ANNOTATION_PROVIDER; -import static java.util.function.Predicate.not; - -/** - * A service (as declared and annotated by @Service.Provider). - *

- * A service may be a {@link java.util.function.Supplier} of instance, or direct contract implementation. - */ -class CoreService { - private static final TypedElementInfo DEFAULT_CONSTRUCTOR = TypedElementInfo.builder() - .typeName(TypeNames.OBJECT) - .accessModifier(AccessModifier.PUBLIC) - .kind(ElementKind.CONSTRUCTOR) - .build(); - - // whether this is an abstract class or not - private final boolean isAbstract; - // If this is a factory or not - private final CoreFactoryType factoryType; - // The class name of the service - private final TypeName serviceType; - // The class name of the service descriptor to be generated - private final TypeName descriptorType; - // If this service extends another service - private final ServiceSuperType superType; - // Required constructor "injection points" of this service - private final List dependencies; - private final CoreTypeConstants constants; - // Contracts provided by this service - private final Set contracts; - private final Set factoryContracts; - - CoreService(boolean isAbstract, - CoreFactoryType factoryType, - TypeName serviceType, - TypeName descriptorType, - ServiceSuperType superType, - List dependencies, - CoreTypeConstants constants, - Set contracts, - Set factoryContracts) { - this.isAbstract = isAbstract; - this.factoryType = factoryType; - this.serviceType = serviceType; - this.descriptorType = descriptorType; - this.superType = superType; - this.dependencies = dependencies; - this.constants = constants; - this.contracts = contracts; - this.factoryContracts = factoryContracts; - } - - static CoreService create(RegistryCodegenContext ctx, - RegistryRoundContext roundContext, - TypeInfo serviceInfo, - Collection allServices) { - - TypeName serviceType = serviceInfo.typeName(); - TypeName descriptorType = ctx.descriptorType(serviceType); - - Set directContracts = new HashSet<>(); - Set providedContracts = new HashSet<>(); - CoreFactoryType factoryType = CoreFactoryType.SERVICE; - - ServiceContracts serviceContracts = roundContext.serviceContracts(serviceInfo); - - // now we know which contracts are OK to use, and we can check the service types and real contracts - // service is a factory only if it implements the interface directly; this is never inherited - List typeInfos = serviceInfo.interfaceTypeInfo(); - Map implementedInterfaceTypes = new HashMap<>(); - typeInfos.forEach(it -> implementedInterfaceTypes.put(it.typeName(), it)); - - var response = serviceContracts.analyseFactory(TypeNames.SUPPLIER); - if (response.valid()) { - factoryType = CoreFactoryType.SUPPLIER; - directContracts.add(ResolvedType.create(response.factoryType())); - providedContracts.addAll(response.providedContracts()); - implementedInterfaceTypes.remove(TypeNames.SUPPLIER); - } - - // add direct contracts - HashSet processedDirectContracts = new HashSet<>(); - implementedInterfaceTypes.forEach((type, typeInfo) -> { - serviceContracts.addContracts(directContracts, - processedDirectContracts, - typeInfo); - }); - - // if we are a factory, our direct contracts are a different set (as it is satisfied by the instance directly - // and not by the factory method) - Set factoryContracts = (factoryType == CoreFactoryType.SUPPLIER) - ? directContracts - : Set.of(); - // and provided contracts are the "real" contracts of the provider - Set contracts = (factoryType == CoreFactoryType.SUPPLIER) - ? providedContracts - : directContracts; - - DependencyResult dependencyResult = gatherDependencies(ctx, serviceInfo); - ServiceSuperType superType = superType(ctx, serviceInfo, allServices); - - // the service metadata must contain all contracts the service provides (both provider and provided) - return new CoreService(isAbstract(serviceInfo), - factoryType, - serviceType, - descriptorType, - superType, - dependencyResult.result(), - dependencyResult.constants(), - contracts, - factoryContracts); - } - - boolean isAbstract() { - return isAbstract; - } - - CoreFactoryType factoryType() { - return factoryType; - } - - TypeName serviceType() { - return serviceType; - } - - TypeName descriptorType() { - return descriptorType; - } - - ServiceSuperType superType() { - return superType; - } - - List dependencies() { - return dependencies; - } - - Set contracts() { - return contracts; - } - - Set factoryContracts() { - return factoryContracts; - } - - CoreTypeConstants constants() { - return constants; - } - - // find super type if it is also a service (or has a service descriptor) - private static ServiceSuperType superType(RegistryCodegenContext ctx, TypeInfo serviceInfo, Collection services) { - Optional maybeSuperType = serviceInfo.superTypeInfo(); - if (maybeSuperType.isEmpty()) { - // this class does not have a super type - return ServiceSuperType.create(); - } - - // check if the super type is part of current annotation processing - TypeInfo superType = maybeSuperType.get(); - TypeName expectedSuperDescriptor = ctx.descriptorType(superType.typeName()); - TypeName superTypeToExtend = TypeName.builder(expectedSuperDescriptor) - .addTypeArgument(TypeName.create("T")) - .build(); - boolean isCore = superType.hasAnnotation(SERVICE_ANNOTATION_PROVIDER); - if (!isCore) { - throw new CodegenException("Service annotated with @Service.Provider extends invalid supertype," - + " the super type must also be a @Service.Provider. Type: " - + serviceInfo.typeName().fqName() + ", super type: " - + superType.typeName().fqName(), - serviceInfo.originatingElementValue()); - } - - for (TypeInfo service : services) { - if (service.typeName().equals(superType.typeName())) { - return ServiceSuperType.create(service, superTypeToExtend); - } - } - // if not found in current list, try checking existing types - return ctx.typeInfo(expectedSuperDescriptor) - .map(it -> ServiceSuperType.create(superType, superTypeToExtend)) - .orElseGet(ServiceSuperType::create); - } - - private static DependencyResult gatherDependencies(RegistryCodegenContext ctx, TypeInfo serviceInfo) { - TypedElementInfo constructor = constructor(serviceInfo); - - // core services only support inversion of control for constructor parameters - AtomicInteger dependencyIndex = new AtomicInteger(); - - List result = new ArrayList<>(); - CoreTypeConstants constants = new CoreTypeConstants(); - - for (TypedElementInfo param : constructor.parameterArguments()) { - result.add(CoreDependency.create(ctx, - constructor, - param, - constants, - dependencyIndex.getAndIncrement())); - } - - return new DependencyResult(result, constants); - } - - private static TypedElementInfo constructor(TypeInfo serviceInfo) { - var allConstructors = serviceInfo.elementInfo() - .stream() - .filter(ElementInfoPredicates::isConstructor) - .collect(Collectors.toUnmodifiableList()); - - if (allConstructors.isEmpty()) { - // no constructor, use default - return DEFAULT_CONSTRUCTOR; - } - - var nonPrivateConstructors = allConstructors.stream() - .filter(not(ElementInfoPredicates::isPrivate)) - .collect(Collectors.toUnmodifiableList()); - - if (nonPrivateConstructors.isEmpty()) { - throw new CodegenException("Service does not contain any non-private constructor", - serviceInfo.originatingElementValue()); - } - if (allConstructors.size() > 1) { - throw new CodegenException("Service contains more than one non-private constructor", - serviceInfo.originatingElementValue()); - } - - return allConstructors.getFirst(); - } - - private static boolean isAbstract(TypeInfo serviceInfo) { - return serviceInfo.elementModifiers().contains(Modifier.ABSTRACT) - && serviceInfo.kind() == ElementKind.CLASS; - } - - private record DependencyResult(List result, CoreTypeConstants constants) { - } -} From 98129f1bd8f5189967ab124ad43a5bea0ac34548 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 28 Nov 2025 12:07:31 +0100 Subject: [PATCH 2/2] Update to declarative documentation, added security. --- .../asciidoc/se/injection/declarative.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/src/main/asciidoc/se/injection/declarative.adoc b/docs/src/main/asciidoc/se/injection/declarative.adoc index cef6584e322..63e2291b908 100644 --- a/docs/src/main/asciidoc/se/injection/declarative.adoc +++ b/docs/src/main/asciidoc/se/injection/declarative.adoc @@ -83,6 +83,7 @@ The following features are currently implemented: - <> - <> - <> +- <> A Helidon Declarative application should be started using the generated application binding, to ensure no lookup and no reflection. The call to `ServiceRegistryManager.start` ensures that all services with a defined `RunLevel` are started, including Helidon WebServer, Scheduled services etc. @@ -371,3 +372,21 @@ For each constraint annotation, there MUST be a service that validates it. ---- include::{sourcedir}/se/inject/DeclarativeExample.java[tag=snippet_10, indent=0] ---- + +=== Security [[Dec-Security]] + +Security provides protection of WebServer endpoints. + +Identity propagation (when using a WebClient) depends on configuration of the client and configuration of security. +We currently do not have declarative way of modifying client behavior. + +Supported annotations: + +- `io.helidon.security.annotations.Authenticated` - mark an endpoint or a method as requiring authentication +- `io.helidon.security.annotations.Authorized` - mark an endpoint or a method as requiring authorization +- `io.helidon.security.annotations.Audited` - mark an endpoint or a method as requiring audit logging +- `io.helidon.security.abac.role.RoleValidator.PermitAll` - annotated method does not require any authentication or authorization (even if endpoint does) +- `jakarta.annotation.security.PermitAll` - same as `RoleValidator.PermitAll` +- `jakarta.annotation.security.DenyAll` - annotated method will not be callable with any kind of authentication or authorization +- link:{security-javadoc-base-url}/io/helidon/security/abac/role/RoleValidator.Roles.html[`io.helidon.security.abac.role.RoleValidator.Roles`] - provide a set of roles that can access a resource, implies authentication is required +- `jakarta.annotation.security.RolesAllowed` - same as above (`RoleValidator.Roles`)