Skip to content

Commit

Permalink
ServicesFactory can now provide any qualified instances (not just nam…
Browse files Browse the repository at this point in the history
…ed) (#9768)
  • Loading branch information
tomas-langer authored Feb 19, 2025
1 parent 5a6a612 commit eb4aac6
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
* Copyright (c) 2022, 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.
Expand Down Expand Up @@ -172,13 +172,21 @@ default boolean matches(ServiceInfo serviceInfo) {
|| this.contracts().contains(ResolvedType.create(serviceInfo.serviceType()))
|| serviceInfo.factoryContracts().containsAll(this.contracts());
}
return matches
matches = matches
&& matchesProviderTypes(factoryTypes(), serviceInfo.factoryType())
&& matchesAbstract(includeAbstract(), serviceInfo.isAbstract())
&& (this.scopes().isEmpty() || this.scopes().contains(serviceInfo.scope()))
&& Qualifiers.matchesQualifiers(serviceInfo.qualifiers(), this.qualifiers())
&& matchesWeight(serviceInfo, this)
&& matchesOptionals(serviceInfo.runLevel(), this.runLevel());

if (serviceInfo.factoryType() == FactoryType.SERVICES) {
// if the service info is a services factory, it may have qualifiers defined at runtime,
// resolve based on instances (i.e. later)
return matches;
}

return matches
&& Qualifiers.matchesQualifiers(serviceInfo.qualifiers(), this.qualifiers());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ static void serviceType(Lookup.BuilderBase<?, ?> builder, Class<?> contract) {
builder.serviceType(TypeName.create(contract));
}

/**
* Only lookup services with the provided named qualifier.
*
* @param builder builder instance
* @param name the name qualifier (use {@link io.helidon.service.registry.Service.Named#WILDCARD_NAME} to find all
*/
@Prototype.BuilderMethod
static void named(Lookup.BuilderBase<?, ?> builder, String name) {
builder.addQualifier(Qualifier.createNamed(name));
}

/**
* Only lookup services with the provided named qualifier, where name is the fully qualified name of the class.
*
* @param builder builder instance
* @param clazz fully qualified name of the class is the name qualifier to use
* @see #named(String)
*/
@Prototype.BuilderMethod
static void named(Lookup.BuilderBase<?, ?> builder, Class<?> clazz) {
builder.addQualifier(Qualifier.createNamed(clazz));
}

private static Lookup createEmpty() {
return Lookup.builder().build();
}
Expand All @@ -133,7 +156,8 @@ public void decorate(Lookup.BuilderBase<?, ?> builder, Optional<Dependency> depe
// clear if contained only IP stuff
boolean shouldClear = builder.qualifiers().equals(existing.qualifiers());

if (!(builder.contracts().contains(ResolvedType.create(existing.contract()))
if (!(
builder.contracts().contains(ResolvedType.create(existing.contract()))
&& builder.contracts().size() == 1)) {
shouldClear = false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (c) 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.
* 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.tests.inject;

import java.util.List;
import java.util.stream.Collectors;

import io.helidon.service.registry.Qualifier;
import io.helidon.service.registry.Service;
import io.helidon.service.registry.ServiceInstance;

final class NamedServicesFactoryTypes {
private NamedServicesFactoryTypes() {
}

interface NamedConfig {
String name();
}

interface TargetType {
NamedConfig config();
}

@Service.Singleton
static class ConfigFactory implements Service.ServicesFactory<NamedConfig> {
private final List<NamedConfig> configs;

@Service.Inject
ConfigFactory() {
configs = List.of(new NamedConfigImpl("first"),
new NamedConfigImpl("second"),
new NamedConfigImpl("third"));
}

ConfigFactory(List<NamedConfig> configs) {
this.configs = configs;
}

@Override
public List<Service.QualifiedInstance<NamedConfig>> services() {
return configs.stream()
.map(it -> Service.QualifiedInstance.create(it, Qualifier.createNamed(it.name())))
.collect(Collectors.toUnmodifiableList());
}
}

@Service.Singleton
@Service.PerInstance(NamedConfig.class)
static class TargetTypeProvider implements TargetType {
private final NamedConfig config;

@Service.Inject
TargetTypeProvider(@Service.InstanceName String name,
NamedConfig config,
List<ServiceInstance<CharSequence>> emptyList) {
this.config = config;
if (!name.equals(config.name())) {
throw new IllegalStateException("Got name: " + name + " but config is named: " + config.name());
}
}

@Override
public NamedConfig config() {
return config;
}
}

@Service.Singleton
static class NamedReceiver {
private final NamedConfig config;
private final List<NamedConfig> all;

@Service.Inject
NamedReceiver(@Service.Named("second") NamedConfig config,
@Service.Named(Service.Named.WILDCARD_NAME) List<NamedConfig> all) {
this.config = config;
this.all = all;
}

String name() {
return config.name();
}

List<String> allNames() {
return all.stream()
.map(NamedConfig::name)
.collect(Collectors.toList());
}
}

static class NamedConfigImpl implements NamedConfig {
private final String name;

NamedConfigImpl(String name) {
this.name = name;
}

@Override
public String name() {
return name;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
* Copyright (c) 2024, 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.
Expand All @@ -16,78 +16,90 @@

package io.helidon.service.tests.inject;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.stream.Collectors;

import io.helidon.common.types.Annotation;
import io.helidon.service.registry.Qualifier;
import io.helidon.service.registry.Service;
import io.helidon.service.registry.ServiceInstance;

final class ServicesFactoryTypes {
private ServicesFactoryTypes() {
}

interface NamedConfig {
String name();
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.CLASS)
@Service.Qualifier
@interface FirstQualifier {
Qualifier QUALIFIER = Qualifier.create(Annotation.create(FirstQualifier.class));
}

interface TargetType {
NamedConfig config();
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.CLASS)
@Service.Qualifier
@interface SecondQualifier {
Qualifier QUALIFIER = Qualifier.create(Annotation.create(SecondQualifier.class));
}

@Service.Singleton
static class ConfigFactory implements Service.ServicesFactory<NamedConfig> {
private final List<NamedConfig> configs;

@Service.Inject
ConfigFactory() {
configs = List.of(new NamedConfigImpl("first"),
new NamedConfigImpl("second"),
new NamedConfigImpl("third"));
}
interface QualifiedContract {
String description();
}

ConfigFactory(List<NamedConfig> configs) {
this.configs = configs;
}
@Service.Singleton
static class ContractFactory implements Service.ServicesFactory<QualifiedContract> {

@Override
public List<Service.QualifiedInstance<NamedConfig>> services() {
return configs.stream()
.map(it -> Service.QualifiedInstance.create(it, Qualifier.createNamed(it.name())))
.collect(Collectors.toUnmodifiableList());
public List<Service.QualifiedInstance<QualifiedContract>> services() {
return List.of(
Service.QualifiedInstance.create(new QualifiedImpl("first"), FirstQualifier.QUALIFIER),
Service.QualifiedInstance.create(new QualifiedImpl("second"), SecondQualifier.QUALIFIER),
Service.QualifiedInstance.create(new QualifiedImpl("both"),
FirstQualifier.QUALIFIER,
SecondQualifier.QUALIFIER)
);
}
}

@Service.Singleton
@Service.PerInstance(NamedConfig.class)
static class TargetTypeProvider implements TargetType {
private final NamedConfig config;
static class QualifiedReceiver {
private final QualifiedContract first;
private final QualifiedContract second;
private final QualifiedContract third;

@Service.Inject
TargetTypeProvider(@Service.InstanceName String name,
NamedConfig config,
List<ServiceInstance<CharSequence>> emptyList) {
this.config = config;
if (!name.equals(config.name())) {
throw new IllegalStateException("Got name: " + name + " but config is named: " + config.name());
}
QualifiedReceiver(@FirstQualifier QualifiedContract first,
@SecondQualifier QualifiedContract second,
@FirstQualifier @SecondQualifier QualifiedContract third) {
this.first = first;
this.second = second;
this.third = third;
}

@Override
public NamedConfig config() {
return config;
QualifiedContract first() {
return first;
}

QualifiedContract second() {
return second;
}

QualifiedContract third() {
return third;
}
}

static class NamedConfigImpl implements NamedConfig {
static class QualifiedImpl implements QualifiedContract {
private final String name;

NamedConfigImpl(String name) {
QualifiedImpl(String name) {
this.name = name;
}

@Override
public String name() {
public String description() {
return name;
}
}
Expand Down
Loading

0 comments on commit eb4aac6

Please sign in to comment.