diff --git a/pico/api/pom.xml b/pico/api/pom.xml index 24c75e702fe..5a44d06b5f7 100644 --- a/pico/api/pom.xml +++ b/pico/api/pom.xml @@ -60,7 +60,6 @@ io.helidon.builder helidon-builder - provided io.helidon.builder diff --git a/pico/api/src/main/java/module-info.java b/pico/api/src/main/java/module-info.java index 0e187653486..b84b56982f0 100644 --- a/pico/api/src/main/java/module-info.java +++ b/pico/api/src/main/java/module-info.java @@ -22,7 +22,7 @@ requires io.helidon.common.types; requires io.helidon.common; requires io.helidon.common.config; - requires static io.helidon.builder; + requires transitive io.helidon.builder; requires static io.helidon.config.metadata; requires static jakarta.annotation; diff --git a/pico/configdriven/runtime/pom.xml b/pico/configdriven/runtime/pom.xml index 4a56b9b7ebb..10cc9272a11 100644 --- a/pico/configdriven/runtime/pom.xml +++ b/pico/configdriven/runtime/pom.xml @@ -84,6 +84,11 @@ helidon-common-testing-junit5 test + + org.mockito + mockito-core + test + diff --git a/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/AbstractConfiguredServiceProvider.java b/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/AbstractConfiguredServiceProvider.java index 67e89488df7..b244a83d958 100644 --- a/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/AbstractConfiguredServiceProvider.java +++ b/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/AbstractConfiguredServiceProvider.java @@ -116,13 +116,13 @@ static Comparator configBeanComparator() { static BindableConfigBeanRegistry resolveConfigBeanRegistry() { HelidonConfigBeanRegistry cbr = ConfigBeanRegistryHolder.configBeanRegistry().orElse(null); if (cbr == null) { - LOGGER.log(System.Logger.Level.INFO, "Config-Driven Services disabled (config bean registry not found)"); + LOGGER.log(System.Logger.Level.INFO, "Config-Driven Services is disabled (config bean registry not found)"); return null; } if (!(cbr instanceof BindableConfigBeanRegistry)) { Optional callingContext = CallingContextFactory.create(false); - String desc = "Config-Driven Services disabled (unsupported implementation): " + cbr; + String desc = "Config-Driven Services is disabled (unsupported implementation): " + cbr; String msg = (callingContext.isEmpty()) ? toErrorMessage(desc) : toErrorMessage(callingContext.get(), desc); throw new PicoException(msg); } diff --git a/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistry.java b/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistry.java index 69f60b66320..ea9c970b18a 100644 --- a/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistry.java +++ b/pico/configdriven/runtime/src/main/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistry.java @@ -123,7 +123,8 @@ static void validateUsingBeanAttributes(Supplier valueSupplier, @Override public boolean reset(boolean deep) { - System.Logger.Level level = isInitialized() ? System.Logger.Level.INFO : System.Logger.Level.DEBUG; + System.Logger.Level level = (isInitialized() && PicoServices.isDebugEnabled()) + ? System.Logger.Level.INFO : System.Logger.Level.DEBUG; LOGGER.log(level, "Resetting"); configuredServiceProviderMetaConfigBeanMap.clear(); configuredServiceProvidersByConfigKey.clear(); @@ -351,28 +352,32 @@ void registerChildConfigBeans(io.helidon.con config.asNodeList() .ifPresent(list -> { list.forEach(beanCfg -> { - // use explicit name, otherwise index or name of the config node (for lists, the name is 0 bases index) - String name = beanCfg.get("name").asString().orElseGet(beanCfg::name); CB configBean = toConfigBean(beanCfg, configuredServiceProvider); - registerConfigBean(configBean, name, beanCfg, configuredServiceProvider, metaAttributes); + String instanceId = toInstanceId(beanCfg); + registerConfigBean(configBean, instanceId, beanCfg, configuredServiceProvider, metaAttributes); }); }); } - - GCB toConfigBean(io.helidon.config.Config config, - ConfiguredServiceProvider configuredServiceProvider) { + static GCB toConfigBean(io.helidon.config.Config config, + ConfiguredServiceProvider configuredServiceProvider) { GCB configBean = (GCB) Objects.requireNonNull(configuredServiceProvider.toConfigBean(config), - "unable to create default config bean for " + configuredServiceProvider); + "Unable to create default config bean for " + configuredServiceProvider); if (configuredServiceProvider instanceof AbstractConfiguredServiceProvider) { - AbstractConfiguredServiceProvider csp = (AbstractConfiguredServiceProvider) configuredServiceProvider; - csp.configBeanInstanceId(configBean, config.key().toString()); - assert (config.name().equals(configBean.__name().orElseThrow())) : csp; + setConfigBeanInstanceId(config, configBean, (AbstractConfiguredServiceProvider) configuredServiceProvider); } return configBean; } + @SuppressWarnings("rawtypes") + static void setConfigBeanInstanceId(io.helidon.config.Config config, + GeneratedConfigBean configBean, + AbstractConfiguredServiceProvider csp) { + String instanceId = toInstanceId(config); + csp.configBeanInstanceId(configBean, instanceId); + } + /** * Validates the config bean against the declared policy, coming by way of annotations on the * {@code ConfiguredOption}'s. @@ -391,8 +396,6 @@ void validate(GCB configBean, Set problems = new LinkedHashSet<>(); String instanceId = csp.toConfigBeanInstanceId(configBean); assert (hasValue(key)); - assert (config == null || DEFAULT_INSTANCE_ID.equals(key) || (config.key().toString().equals(key))) - : key + " and " + config.key().toString(); AttributeVisitor visitor = new AttributeVisitor<>() { @Override @@ -458,7 +461,7 @@ void registerConfigBean(GCB configBean, if (instanceId != null) { csp.configBeanInstanceId(configBean, instanceId); } else { - instanceId = configuredServiceProvider.toConfigBeanInstanceId((GCB) configBean); + instanceId = configuredServiceProvider.toConfigBeanInstanceId(configBean); } if (DEFAULT_INSTANCE_ID.equals(instanceId)) { @@ -586,4 +589,10 @@ private void visitAndInitialize(List configs, }); } + static String toInstanceId(io.helidon.config.Config config) { + // use explicit name, otherwise index or fq id of the config node (for lists, the name is 0 bases index) + String id = config.get("name").asString().orElseGet(config::name); + return hasValue(id) ? id : DEFAULT_INSTANCE_ID; + } + } diff --git a/pico/configdriven/runtime/src/test/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistryTest.java b/pico/configdriven/runtime/src/test/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistryTest.java new file mode 100644 index 00000000000..27e90ead8c5 --- /dev/null +++ b/pico/configdriven/runtime/src/test/java/io/helidon/pico/configdriven/runtime/DefaultPicoConfigBeanRegistryTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 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.pico.configdriven.runtime; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import io.helidon.builder.config.spi.GeneratedConfigBeanBase; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +import org.junit.jupiter.api.Test; + +import static io.helidon.pico.configdriven.runtime.DefaultPicoConfigBeanRegistry.DEFAULT_INSTANCE_ID; +import static io.helidon.pico.configdriven.runtime.DefaultPicoConfigBeanRegistry.setConfigBeanInstanceId; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class DefaultPicoConfigBeanRegistryTest { + + @Test + void testsetConfigBeanInstanceId() { + // the setup + AtomicInteger setCalls = new AtomicInteger(); + AtomicReference instanceId = new AtomicReference<>(DEFAULT_INSTANCE_ID); + + GeneratedConfigBeanBase cfgBean = mock(GeneratedConfigBeanBase.class); + when(cfgBean.__instanceId()).thenAnswer(im -> instanceId.get()); + cfgBean.__instanceId(anyString()); + doAnswer(im -> { + setCalls.incrementAndGet(); + instanceId.set(im.getArgument(1).toString()); + return null; + }).when(cfgBean).__instanceId(anyString()); + + Config config = Config.builder( + ConfigSources.create( + Map.of("other.than.name", "the-name"), "config-root-plus-one-socket")) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + + AbstractConfiguredServiceProvider csp = mock(AbstractConfiguredServiceProvider.class); + doAnswer(im -> { + setCalls.incrementAndGet(); + instanceId.set(im.getArgument(1).toString()); + return null; + }).when(csp).configBeanInstanceId(any(), anyString()); + when(csp.configBean()).thenAnswer(im -> Optional.of(cfgBean)); + + // the 1st test + setConfigBeanInstanceId(config, cfgBean, csp); + assertThat(instanceId.get(), + equalTo(DEFAULT_INSTANCE_ID)); + assertThat(setCalls.get(), + is(1)); + + // reset + setCalls.set(0); + config = Config.builder( + ConfigSources.create( + Map.of("name", "the-name"), "config-root-plus-one-socket")) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + + // the next test + setConfigBeanInstanceId(config, cfgBean, csp); + assertThat(instanceId.get(), + equalTo("the-name")); + assertThat(setCalls.get(), + is(1)); + } + +} diff --git a/pico/configdriven/tests/configuredby/src/main/java/io/helidon/pico/configdriven/configuredby/test/AbstractConfiguredByTest.java b/pico/configdriven/tests/configuredby/src/main/java/io/helidon/pico/configdriven/configuredby/test/AbstractConfiguredByTest.java index dc164db9194..0a57cb2d408 100644 --- a/pico/configdriven/tests/configuredby/src/main/java/io/helidon/pico/configdriven/configuredby/test/AbstractConfiguredByTest.java +++ b/pico/configdriven/tests/configuredby/src/main/java/io/helidon/pico/configdriven/configuredby/test/AbstractConfiguredByTest.java @@ -88,7 +88,7 @@ public MapConfigSource.Builder createBasicTestingConfigSource() { public MapConfigSource.Builder createRootDefault8080TestingConfigSource() { return ConfigSources.create( Map.of( - FAKE_SERVER_CONFIG + ".name", "root", + FAKE_SERVER_CONFIG + ".name", "fake-server", FAKE_SERVER_CONFIG + ".port", "8080", FAKE_SERVER_CONFIG + ".worker-count", "1" ), "config-root-default-8080"); diff --git a/pico/configdriven/tests/configuredby/src/test/java/io/helidon/pico/configdriven/configuredby/test/ConfiguredByTest.java b/pico/configdriven/tests/configuredby/src/test/java/io/helidon/pico/configdriven/configuredby/test/ConfiguredByTest.java index bb79e48238f..aeacebba966 100644 --- a/pico/configdriven/tests/configuredby/src/test/java/io/helidon/pico/configdriven/configuredby/test/ConfiguredByTest.java +++ b/pico/configdriven/tests/configuredby/src/test/java/io/helidon/pico/configdriven/configuredby/test/ConfiguredByTest.java @@ -54,7 +54,6 @@ protected MapConfigSource.Builder createNested8080TestingConfigSource() { public MapConfigSource.Builder createRootPlusOneSocketTestingConfigSource() { return ConfigSources.create( Map.of( - FAKE_SERVER_CONFIG + ".name", "root", FAKE_SERVER_CONFIG + ".port", "8080", FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".0.name", "first", FAKE_SERVER_CONFIG + "." + FAKE_SOCKET_CONFIG + ".0.port", "8081" diff --git a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractApplicationCreatorMojo.java b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractApplicationCreatorMojo.java index 3877c1ebada..a1dccf9260c 100644 --- a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractApplicationCreatorMojo.java +++ b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractApplicationCreatorMojo.java @@ -98,14 +98,14 @@ public abstract class AbstractApplicationCreatorMojo extends AbstractCreatorMojo /** * Sets the named types permitted for providers, assuming use of - * {@link PermittedProviderType#NAMED}. + * {@link io.helidon.pico.tools.ApplicationCreatorConfigOptions.PermittedProviderType#NAMED}. */ @Parameter(property = PicoServicesConfig.NAME + ".permitted.provider.type.names", readonly = true) private List permittedProviderTypeNames; /** * Sets the named qualifier types permitted for providers, assuming use of - * {@link PermittedProviderType#NAMED}. + * {@link io.helidon.pico.tools.ApplicationCreatorConfigOptions.PermittedProviderType#NAMED}. */ @Parameter(property = PicoServicesConfig.NAME + ".permitted.provider.qualifier.type.names", readonly = true) private List permittedProviderQualifierTypeNames; @@ -134,8 +134,7 @@ String getThisModuleName() { // try to recover it from a previous tooling step String appPackageName = loadAppPackageName().orElse(null); if (appPackageName == null) { - // throw noModuleFoundError(); - getLog().warn(noModuleFoundError().getMessage()); + getLog().info(noModuleFoundError().getMessage()); } else { moduleName = appPackageName; } @@ -143,9 +142,14 @@ String getThisModuleName() { return moduleName; } - ServiceProvider lookupThisModule(String name, - Services services) { - return services.lookupFirst(ModuleComponent.class, name, false).orElseThrow(() -> noModuleFoundError(name)); + Optional> lookupThisModule(String name, + Services services, + boolean expected) { + Optional> result = services.lookupFirst(ModuleComponent.class, name, false); + if (result.isEmpty() && expected) { + throw noModuleFoundError(name); + } + return result; } String getClassPrefixName() { @@ -250,7 +254,7 @@ protected void innerExecute() { PicoServices picoServices = picoServices(false); if (picoServices.config().usesCompileTimeApplications()) { - String desc = "should not be using 'application' bindings"; + String desc = "Should not be using 'application' bindings"; String msg = (callCtx == null) ? toErrorMessage(desc) : toErrorMessage(callCtx, desc); throw new IllegalStateException(msg); } @@ -263,9 +267,13 @@ protected void innerExecute() { .lookupAll(ServiceInfoCriteriaDefault.builder() .addContractImplemented(ModuleComponent.class.getName()) .build()); - getLog().info("processing modules: " + toDescriptions(allModules)); + if (PicoServices.isDebugEnabled()) { + getLog().info("processing modules: " + toDescriptions(allModules)); + } else { + getLog().debug("processing modules: " + toDescriptions(allModules)); + } if (allModules.isEmpty()) { - warn("no modules to process"); + warn("No modules to process"); } // retrieves all the services in the registry @@ -288,8 +296,8 @@ protected void innerExecute() { ? moduleInfoPathRef.get().getPath() : null; String moduleInfoModuleName = getThisModuleName(); - ServiceProvider moduleSp = lookupThisModule(moduleInfoModuleName, services); - String packageName = determinePackageName(Optional.ofNullable(moduleSp), serviceTypeNames, descriptor, true); + Optional> moduleSp = lookupThisModule(moduleInfoModuleName, services, false); + String packageName = determinePackageName(moduleSp, serviceTypeNames, descriptor, true); CodeGenPaths codeGenPaths = CodeGenPathsDefault.builder() .generatedSourcesPath(getGeneratedSourceDirectory().getPath()) diff --git a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractCreatorMojo.java b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractCreatorMojo.java index f6e9c562996..eb0269bea96 100644 --- a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractCreatorMojo.java +++ b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/AbstractCreatorMojo.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; -import java.util.Objects; import java.util.Optional; import io.helidon.common.types.TypeName; @@ -35,6 +34,7 @@ import io.helidon.pico.tools.ModuleUtils; import io.helidon.pico.tools.Options; import io.helidon.pico.tools.TemplateHelper; +import io.helidon.pico.tools.ToolsException; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; @@ -66,6 +66,8 @@ public abstract class AbstractCreatorMojo extends AbstractMojo { */ static final String TAG_FAIL_ON_WARNING = PicoServicesConfig.NAME + ".failOnWarning"; + static final String TAG_PACKAGE_NAME = PicoServicesConfig.NAME + ".package.name"; + /** * The file name written to ./target/pico/ to track the last package name generated for this application. * This application package name is what we fall back to for the application name and the module name if not otherwise @@ -135,7 +137,7 @@ public abstract class AbstractCreatorMojo extends AbstractMojo { /** * The package name to apply. If not found the package name will be inferred. */ - @Parameter(property = PicoServicesConfig.NAME + ".package.name", readonly = true) + @Parameter(property = TAG_PACKAGE_NAME, readonly = true) private String packageName; /** @@ -284,7 +286,10 @@ protected String determinePackageName(Optional> } } - Objects.requireNonNull(packageName, "unable to determine package name"); + if (packageName == null) { + throw new ToolsException("Unable to determine the package name. The package name can be set using " + + TAG_PACKAGE_NAME); + } if (persistIt) { // record it to scratch file for later consumption (during test build for example) diff --git a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecHandler.java b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecHandler.java index 928230835a1..a7d028060c4 100644 --- a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecHandler.java +++ b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecHandler.java @@ -86,7 +86,7 @@ Res apply(Supplier reqSupplier, } result.set(fn.apply(req)); } catch (Throwable t) { - throw new ToolsException("error in apply", t); + throw new ToolsException("An error occurred in apply", t); } finally { latch.countDown(); } diff --git a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecutableClassLoader.java b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecutableClassLoader.java index 91594fa54ec..413b2aa641b 100644 --- a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecutableClassLoader.java +++ b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/ExecutableClassLoader.java @@ -49,7 +49,7 @@ public static URLClassLoader create(Collection classPath, urls.add(dependency.toUri().toURL()); } } catch (MalformedURLException e) { - throw new ToolsException("unable to build classpath", e); + throw new ToolsException("Unable to build the classpath", e); } if (parent == null) { diff --git a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/TestApplicationCreatorMojo.java b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/TestApplicationCreatorMojo.java index 41ab9580a67..1f8901ef149 100644 --- a/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/TestApplicationCreatorMojo.java +++ b/pico/maven-plugin/src/main/java/io/helidon/pico/maven/plugin/TestApplicationCreatorMojo.java @@ -55,7 +55,7 @@ public class TestApplicationCreatorMojo extends AbstractApplicationCreatorMojo { /** - * The classname to use for the Pico {@link io.helidon.pico.Application} test class. + * The classname to use for the Pico {@link io.helidon.pico.api.Application} test class. * If not found the classname will be inferred. */ @Parameter(property = PicoServicesConfig.FQN + ".application.class.name", readonly = true diff --git a/pico/runtime/src/main/java/io/helidon/pico/runtime/AbstractServiceProvider.java b/pico/runtime/src/main/java/io/helidon/pico/runtime/AbstractServiceProvider.java index ae8789c445f..e8ace90d5cc 100644 --- a/pico/runtime/src/main/java/io/helidon/pico/runtime/AbstractServiceProvider.java +++ b/pico/runtime/src/main/java/io/helidon/pico/runtime/AbstractServiceProvider.java @@ -617,14 +617,16 @@ public boolean reset(boolean deep) { didAcquire = activationSemaphore.tryAcquire(1, TimeUnit.MILLISECONDS); if (service != null) { - logger().log(System.Logger.Level.INFO, "resetting " + this); + System.Logger.Level level = (PicoServices.isDebugEnabled()) + ? System.Logger.Level.INFO : System.Logger.Level.DEBUG; + logger().log(level, "Resetting " + this); if (deep && service instanceof Resettable) { try { if (((Resettable) service).reset(deep)) { result = true; } } catch (Throwable t) { - logger().log(System.Logger.Level.WARNING, "unable to reset: " + this, t); // eat it + logger().log(System.Logger.Level.WARNING, "Unable to reset: " + this, t); // eat it } } } diff --git a/pico/runtime/src/main/java/io/helidon/pico/runtime/DefaultInjectionPlans.java b/pico/runtime/src/main/java/io/helidon/pico/runtime/DefaultInjectionPlans.java index 73f257951eb..18674757dad 100644 --- a/pico/runtime/src/main/java/io/helidon/pico/runtime/DefaultInjectionPlans.java +++ b/pico/runtime/src/main/java/io/helidon/pico/runtime/DefaultInjectionPlans.java @@ -32,6 +32,7 @@ import io.helidon.pico.api.DependencyInfo; import io.helidon.pico.api.InjectionException; import io.helidon.pico.api.InjectionPointInfo; +import io.helidon.pico.api.InjectionPointProvider; import io.helidon.pico.api.Interceptor; import io.helidon.pico.api.PicoServiceProviderException; import io.helidon.pico.api.PicoServices; @@ -136,15 +137,17 @@ private static void accumulate(DependencyInfo dep, } List> tmpServiceProviders = services.lookupAll(depTo, false); - if (tmpServiceProviders == null || tmpServiceProviders.isEmpty()) { + if (tmpServiceProviders.isEmpty()) { if (VoidServiceProvider.INSTANCE.serviceInfo().matches(depTo)) { tmpServiceProviders = VoidServiceProvider.LIST_INSTANCE; + } else { + tmpServiceProviders = injectionPointProvidersFor(services, depTo); } } // filter down the selections to not include self List> serviceProviders = - (tmpServiceProviders != null && !tmpServiceProviders.isEmpty()) + (!tmpServiceProviders.isEmpty()) ? tmpServiceProviders.stream() .filter(sp -> !isSelf(self, sp)) .collect(Collectors.toList()) @@ -180,6 +183,35 @@ private static void accumulate(DependencyInfo dep, }); } + /** + * Special case where we have qualifiers on the criteria, but we should still allow any + * {@link io.helidon.pico.api.InjectionPointProvider} match regardless, since it will be the ultimate determining agent + * to use the dependency info - not the services registry default logic. + * + * @param services the services registry + * @param depTo the dependency with the qualifiers + * @return a list of {@link io.helidon.pico.api.InjectionPointProvider}s that can handle the contracts requested + */ + static List> injectionPointProvidersFor(Services services, + ServiceInfoCriteria depTo) { + if (depTo.qualifiers().isEmpty()) { + return List.of(); + } + + if (depTo.contractsImplemented().isEmpty()) { + return List.of(); + } + + ServiceInfoCriteria modifiedDepTo = ServiceInfoCriteriaDefault.toBuilder(depTo) + .qualifiers(List.of()) + .build(); + + List> providers = services.lookupAll(modifiedDepTo); + return providers.stream() + .filter(it -> it.serviceInfo().contractsImplemented().contains(InjectionPointProvider.class.getName())) + .toList(); + } + /** * Creates and maybe adjusts the criteria to match the context of who is doing the lookup. * @@ -216,7 +248,7 @@ static ServiceInfoCriteria toCriteria(DependencyInfo dep, * * @param self the reference to the service provider associated with this plan * @param ipInfo the injection point - * @param serviceProviders the service providers that qualify + * @param serviceProviders the service providers that qualify in preferred weighted order * @param logger the logger to use for any logging * @return the resolution (and activation) of the qualified service provider(s) in the form acceptable to the injection point */ diff --git a/pico/runtime/src/test/java/io/helidon/pico/runtime/DefaultInjectionPlansTest.java b/pico/runtime/src/test/java/io/helidon/pico/runtime/DefaultInjectionPlansTest.java new file mode 100644 index 00000000000..8efc4fadedb --- /dev/null +++ b/pico/runtime/src/test/java/io/helidon/pico/runtime/DefaultInjectionPlansTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 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.pico.runtime; + +import java.io.Closeable; +import java.util.List; +import java.util.Map; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.pico.api.BootstrapDefault; +import io.helidon.pico.api.ModuleComponent; +import io.helidon.pico.api.PicoServices; +import io.helidon.pico.api.PicoServicesConfig; +import io.helidon.pico.api.ServiceBinder; +import io.helidon.pico.api.ServiceInfoCriteria; +import io.helidon.pico.api.ServiceInfoCriteriaDefault; +import io.helidon.pico.api.ServiceInfoDefault; +import io.helidon.pico.api.ServiceProvider; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static io.helidon.pico.api.QualifierAndValueDefault.createNamed; +import static io.helidon.pico.runtime.DefaultInjectionPlans.injectionPointProvidersFor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +class DefaultInjectionPlansTest { + static final FakeInjectionPointProviderActivator sp1 = new FakeInjectionPointProviderActivator(); + static final FakeRegularActivator sp2 = new FakeRegularActivator(); + + Config config = Config.builder( + ConfigSources.create( + Map.of(PicoServicesConfig.NAME + "." + PicoServicesConfig.KEY_PERMITS_DYNAMIC, "true"), "config-1")) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + + @BeforeEach + void init() { + PicoServices.globalBootstrap(BootstrapDefault.builder().config(config).build()); + } + + @AfterEach + void tearDown() { + SimplePicoTestingSupport.resetAll(); + } + + /** + * Also exercised in examples/pico. + */ + @Test + void testinjectionPointResolversFor() { + PicoServices picoServices = PicoServices.picoServices().orElseThrow(); + DefaultServices services = (DefaultServices) PicoServices.realizedServices(); + services.bind(picoServices, new FakeModuleComponent(), true); + + ServiceInfoCriteria criteria = ServiceInfoCriteriaDefault.builder() + .addQualifier(createNamed("whatever")) + .addContractImplemented(Closeable.class.getName()) + .build(); + List result = injectionPointProvidersFor(services, criteria).stream() + .map(ServiceProvider::description).toList(); + assertThat(result, + contains(sp1.description())); + } + + static class FakeModuleComponent implements ModuleComponent { + @Override + public void configure(ServiceBinder binder) { + binder.bind(sp1); + binder.bind(sp2); + } + } + + static class FakeInjectionPointProviderActivator extends AbstractServiceProvider { + private static final ServiceInfoDefault serviceInfo = + ServiceInfoDefault.builder() + .serviceTypeName(FakeInjectionPointProviderActivator.class.getName()) + .addContractsImplemented(Closeable.class.getName()) + .addExternalContractsImplemented(io.helidon.pico.api.InjectionPointProvider.class.getName()) + .addExternalContractsImplemented(jakarta.inject.Provider.class.getName()) + .build(); + + public FakeInjectionPointProviderActivator() { + serviceInfo(serviceInfo); + } + } + + static class FakeRegularActivator extends AbstractServiceProvider { + private static final ServiceInfoDefault serviceInfo = + ServiceInfoDefault.builder() + .serviceTypeName(FakeRegularActivator.class.getName()) + .addContractsImplemented(Closeable.class.getName()) + .addExternalContractsImplemented(jakarta.inject.Provider.class.getName()) + .build(); + + public FakeRegularActivator() { + serviceInfo(serviceInfo); + } + } + +} diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java b/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java index 209a2224927..950e1fdbad8 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java @@ -513,7 +513,7 @@ Optional toSourceLocation(String name) { } } - messager().warn(CodeGenFiler.class.getSimpleName() + ": unable to determine source location for: " + name); + messager().log(CodeGenFiler.class.getSimpleName() + ": unable to determine source location for: " + name); return Optional.empty(); }