All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.integrations.oci.sdk.cdi.OciExtension Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
/*
 * 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.
 * 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.integrations.oci.sdk.cdi;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.System.Logger;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.oracle.bmc.ConfigFileReader.ConfigFile;
import com.oracle.bmc.Service;
import com.oracle.bmc.auth.AbstractAuthenticationDetailsProvider;
import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider;
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider;
import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider.InstancePrincipalsAuthenticationDetailsProviderBuilder;
import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider;
import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider.ResourcePrincipalAuthenticationDetailsProviderBuilder;
import com.oracle.bmc.auth.SessionTokenAuthenticationDetailsProvider;
import com.oracle.bmc.auth.SessionTokenAuthenticationDetailsProvider.SessionTokenAuthenticationDetailsProviderBuilder;
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider.SimpleAuthenticationDetailsProviderBuilder;
import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider;
import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder;
import com.oracle.bmc.common.ClientBuilderBase;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.AmbiguousResolutionException;
import jakarta.enterprise.inject.CreationException;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.UnsatisfiedResolutionException;
import jakarta.enterprise.inject.literal.NamedLiteral;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.AfterDeploymentValidation;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.enterprise.inject.spi.ProcessInjectionPoint;
import jakarta.enterprise.util.TypeLiteral;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import static io.helidon.integrations.oci.sdk.cdi.AdpStrategyDescriptors.DEFAULT_ORDERED_ADP_STRATEGY_DESCRIPTORS;
import static io.helidon.integrations.oci.sdk.cdi.AdpStrategyDescriptors.replaceElements;
import static io.helidon.integrations.oci.sdk.cdi.AdpStrategyDescriptors.strategyDescriptors;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.WARNING;
import static java.lang.invoke.MethodType.methodType;

/**
 * A CDI portable
 * extension that enables the {@linkplain jakarta.inject.Inject injection} of any service interface, service client, service client builder, asynchronous service interface, asynchronous service client, or asynchronous service client builder from the Oracle Cloud Infrastructure Java
 * SDK.
 *
 * 

Terminology

* *

The terms service interface, service client, service client builder, asynchronous * service interface, asynchronous service client, and asynchronous service client builder are * defined as follows:

* *
* *
Service
* *
An Oracle Cloud Infrastructure service supported by the Oracle Cloud Infrastructure Java SDK.
* *
Service interface
* *
A Java interface describing the functionality of a service. Distinguished from an asynchronous service interface. * *

For a hypothetical service named Cloud Example, the corresponding service interface will be in a * package named com.oracle.bmc.cloudexample. The service interface's * {@linkplain Class#getSimpleName() simple name} is often also named after the service, * e.g. CloudExample, but need not be.

* *
Service client
* *
A concrete Java class that implements the service interface and has the same * {@linkplain Class#getPackageName() package name} as it. Distinguished from an asynchronous service client. * *

The service client's {@linkplain Class#getSimpleName() simple name} is formed by appending the {@linkplain * Class#getSimpleName() simple name} of the service interface with Client. The {@linkplain Class#getName() * class name} for the service client for the hypothetical {@code com.oracle.bmc.cloudexample.CloudExample} service * interface described above will thus be * com.oracle.bmc.cloudexample.CloudExampleClient.

* *
Service client builder
* *
A concrete Java "builder" class that creates possibly customized instances of its corresponding service client. Distinguished from an asynchronous service client builder. * *

The service client builder is nearly always a nested class of the service client whose instances it builds with a * {@linkplain Class#getSimpleName() simple name} of {@code Builder}. (In the single case in the entirety of the Oracle Cloud Infrastructure Java SDK where this pattern is not * followed, the service client builder's {@linkplain Class#getSimpleName() simple name} is formed by adding {@code * Builder} to the service client's {@linkplain Class#getSimpleName() simple name}.) The {@linkplain Class#getName() * class name} for the service client builder for the hypothetical {@code * com.oracle.bmc.cloudexample.CloudExampleClient} service client described above will thus be * com.oracle.bmc.cloudexample.CloudExampleClient$Builder.

* *
Asynchronous service interface
* *
A Java interface describing the functionality of a service. Distinguished from a service interface. * *

For a hypothetical service named Cloud Example, the corresponding service interface will be in * the same package as that of the service interface. The asynchronous service interface's {@linkplain * Class#getSimpleName() simple name} is formed by adding {@code Async} to the service interface's {@linkplain * Class#getSimpleName() simple name}. The {@linkplain Class#getName() class name} for the asynchronous service * interface for the hypothetical {@code com.oracle.bmc.cloudexample.CloudExample} service interface described above * will thus be * com.oracle.bmc.cloudexample.CloudExampleAsync.

* *
Asynchronous service client
* *
A concrete Java class that implements the asynchronous service interface * and has the same {@linkplain Class#getPackageName() package name} as it. Distinguised from a service client. * *

The asynchronous service client's {@linkplain Class#getSimpleName() simple name} is formed by appending the * {@linkplain Class#getSimpleName() simple name} of the asynchronous service interface with Client. The * {@linkplain Class#getName() class name} for the asynchronous service client for the hypothetical {@code * com.oracle.bmc.cloudexample.CloudExample} asynchronous service interface described above will thus be * com.oracle.bmc.cloudexample.CloudExampleAsyncClient.

* *
Asynchronous service client builder
* *
A concrete Java "builder" class that creates possibly customized instances of its corresponding asynchronous service client. Distinguished from a service client builder. * *

The asynchronous service client builder is nearly always a nested class of the asynchronous service client whose * instances it builds with a {@linkplain Class#getSimpleName() simple name} of {@code Builder}. (In the single case in the entirety of the Oracle Cloud Infrastructure Java SDK where this pattern is not * followed, the asynchronous service client builder's {@linkplain Class#getName() class name} is formed by adding * {@code Builder} to the asynchronous service client's {@linkplain Class#getName() class name}.) The {@linkplain * Class#getName() class name} for the service client builder for the hypothetical {@code * com.oracle.bmc.cloudexample.CloudExampleAsyncClient} service client described above will thus be * com.oracle.bmc.cloudexample.CloudExampleAsyncClient$Builder.

* *
* *

Additionally, for any given service interface, service client, service client builder, asynchronous service * interface, asynchronous service client, or asynchronous service client builder, this {@linkplain Extension extension} * also enables the {@linkplain jakarta.inject.Inject injection} of an appropriate {@link * AbstractAuthenticationDetailsProvider}, which allows the corresponding service client to authenticate with the * service.

* *

In all cases, user-supplied beans will be preferred over any otherwise installed by this {@linkplain Extension * extension}.

* *

Basic Usage

* *

To use this extension, make sure it is on your project's runtime classpath. To {@linkplain jakarta.inject.Inject * inject} a service interface named * com.oracle.bmc.cloudexample.CloudExample (or an analogous asynchronous service interface), you will also need to ensure that its containing * artifact is on your compile classpath (e.g. oci-java-sdk-cloudexample-$VERSION.jar, * where {@code $VERSION} should be replaced by a suitable version number).

* *

Advanced Usage and Customization

* *

In the course of providing {@linkplain jakarta.inject.Inject injection support} for a service interface or an asynchronous service * interface, this {@linkplain Extension extension} will also create service * client builder and asynchronous service client builder instances by * invoking the {@code static} {@code builder()} method that is present on all service * client classes, and will then add those instances as beans. The resulting service client or asynchronous service * client will be built by that builder's {@link ClientBuilderBase#build(AbstractAuthenticationDetailsProvider) * build(AbstractAuthenticationDetailsProvider)} method and will itself be added as a bean.

* *

A user may wish to customize this builder so that the resulting service client or asynchronous service client * reflects the customization. She has two options:

* *
    * *
  1. She may supply her own bean with the service client builder type (or asynchronous client builder type) as one of * its bean * types. In this case, this {@linkplain Extension extension} does not supply the service client builder (or * asynchronous service client builder) and the user is in full control of how her service client (or asynchronous * service client) is constructed.
  2. * *
  3. She may customize the service client builder (or asynchronous service client builder) supplied by this * {@linkplain Extension extension}. To do this, she declares an * observer method that observes the service client builder object (or asynchronous service client builder object) * that is returned from the {@code static} service client (or asynchronous service client) {@code builder()} method. * In her observer method, she may call any method on the supplied service client builder (or asynchronous service * client builder) and her customizations will be retained.
  4. * *
* *

Configuration

* *

This extension uses the following MicroProfile * Config property names (note, however, that no configuration is required):

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
MicroProfile * Config property names
NameTypeDescriptionDefault ValueNotes
{@code oci.auth-strategies}{@link String String[]}A comma-separated list of descriptors describing the strategy, or strategies, to use to select an * appropriate {@link AbstractAuthenticationDetailsProvider} when one is called for.{@code auto}Zero or more of the following: * *
    *
  • {@code auto}
  • *
  • {@code config}
  • *
  • {@code config-file}
  • *
  • {@code instance-principals}
  • *
  • {@code oke-workload-identity}
  • *
  • {@code resource-principal}
  • *
  • {@code session-token-builder}
  • *
  • {@code session-token-config-file}
  • *
* *

A strategy descriptor of {@code config} will cause a {@link * SimpleAuthenticationDetailsProvider} to be used, populated with other MicroProfile Config properties * described here.

* *

A strategy descriptor of {@code config-file} will cause a {@link * ConfigFileAuthenticationDetailsProvider} to be used, {@linkplain * ConfigFileAuthenticationDetailsProvider#ConfigFileAuthenticationDetailsProvider(ConfigFile) loaded from} an * OCI configuration file, * with a loading process customized with other MicroProfile Config properties described here.

* *

A strategy descriptor of {@code instance-principals} will cause an {@link * InstancePrincipalsAuthenticationDetailsProvider} to be used.

* *

A strategy descriptor of {@code oke-workload-identity} will cause a {@link * OkeWorkloadIdentityAuthenticationDetailsProvider} to be used.

* *

A strategy descriptor of {@code resource-principal} will cause a {@link * ResourcePrincipalAuthenticationDetailsProvider} to be used.

* *

A strategy descriptor of {@code session-token-builder} will cause a {@link * SessionTokenAuthenticationDetailsProvider} to be used, {@linkplain * SessionTokenAuthenticationDetailsProviderBuilder#build() built} from a {@link * SessionTokenAuthenticationDetailsProviderBuilder SessionTokenAuthenticationDetailsProviderBuilder} instance, * customizable using other facilities described in this documentation.

* *

A strategy descriptor of {@code session-token-config-file} will cause a {@link * SessionTokenAuthenticationDetailsProvider} to be used, {@linkplain * SessionTokenAuthenticationDetailsProvider#SessionTokenAuthenticationDetailsProvider(ConfigFile) loaded from} * an OCI configuration * file, with a loading process customized with other MicroProfile Config properties described here.

* *

If there are many strategy descriptors supplied, the first one that is deemed to be available or suitable * will be used and all others will be ignored.

* *

If {@code auto} is present in the list, or if no value for this property can be found, * the behavior will be as if {@code auto} were replaced with {@code * config,config-file,session-token-config-file,session-token-builder,instance-principals,resource-principal,oke-workload-identity} * instead. (The replacement values are subject to change in subsequent revisions of this class.)

{@code oci.config.path}{@link String}A {@link String} that is {@linkplain com.oracle.bmc.ConfigFileReader#parse(String) a path to a valid OCI * configuration file} A {@linkplain com.oracle.bmc.ConfigFileReader#parseDefault() default * location}This configuration property has an effect only when either {@code config-file} or {@code * session-token-config-file} is, explicitly or implicitly, present in the value for the {@code * oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.config.profile}{@link String}An OCI configuration file profile.{@link com.oracle.bmc.ConfigFileReader#DEFAULT_PROFILE_NAME DEFAULT}This configuration property has an effect only when either {@code config-file} or {@code * session-token-config-file} is, explicitly or implicitly, present in the value for the {@code * oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.auth.fingerprint}{@link String}An API signing key's fingerprint.This configuration property has an effect only when {@code config} is, explicitly or implicitly, present in * the value for the {@code oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.auth.region}{@link com.oracle.bmc.Region} ({@link String} representation)A region identifier.This configuration property has an effect only when {@code config} is, explicitly or implicitly, present in * the value for the {@code oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.auth.tenant-id}{@link String}An OCID of a tenancy.This configuration property has an effect only when {@code config} is, explicitly or implicitly, present in * the value for the {@code oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.auth.user-id}{@link String}An OCID of a user.This configuration property has an effect only when {@code config} is, explicitly or implicitly, present in * the value for the {@code oci.auth-strategies} configuration property described elsewhere in this table.
{@code oci.extension.classname-vetoes}{@link String String[]}A comma-separated list of {@linkplain Class#getName() class names} beginning with {@code com.oracle.bmc.} * that should be skipped, even if they match the service pattern described above.It is recommended not to supply a value for this property name except in extraordinary circumstances.
{@code oci.extension.lenient-classloading}{@link Boolean boolean}If {@code true}, classes that cannot be loaded will not cause a definition error and will simply be skipped * (recommended).{@code true}It is recommended not to supply a value for this property name except in extraordinary circumstances.
* * @see Extension * * @see Oracle Cloud * Infrastructure Java SDK */ public final class OciExtension implements Extension { /* * Static fields. */ /* * Type and TypeLiteral constants, sorted as alphabetically as possible. */ private static final TypeLiteral> ADP_SUPPLIER_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADP_SUPPLIER_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADP_SUPPLIER_CONFIG_FILE_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADP_SUPPLIER_CONFIG_FILE_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_CONFIG_FILE_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADP_SUPPLIER_INSTANCE_PRINCIPALS_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final TypeLiteral> ADPSUPPLIER_OKEWORKLOADIDENTITYAUTHENTICATIONDETAILSPROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADPSUPPLIER_OKEWORKLOADIDENTITYAUTHENTICATIONDETAILSPROVIDER_TYPE = (ParameterizedType) ADPSUPPLIER_OKEWORKLOADIDENTITYAUTHENTICATIONDETAILSPROVIDER_TYPE_LITERAL.getType(); private static final ParameterizedType ADP_SUPPLIER_INSTANCE_PRINCIPALS_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_INSTANCE_PRINCIPALS_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADP_SUPPLIER_RESOURCE_PRINCIPAL_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADP_SUPPLIER_RESOURCE_PRINCIPAL_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_RESOURCE_PRINCIPAL_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADP_SUPPLIER_SIMPLE_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADP_SUPPLIER_SIMPLE_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADP_SUPPLIER_SIMPLE_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> ADPSUPPLIER_WILDCARD_EXTENDS_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType ADPSUPPLIER_WILDCARD_EXTENDS_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE = (ParameterizedType) ADPSUPPLIER_WILDCARD_EXTENDS_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL.getType(); private static final TypeLiteral> EVENT_OBJECT_TYPE_LITERAL = new TypeLiteral<>() {}; private static final TypeLiteral> SUPPLIER_CONFIGFILE_TYPE_LITERAL = new TypeLiteral<>() {}; private static final ParameterizedType SUPPLIER_CONFIGFILE_TYPE = (ParameterizedType) SUPPLIER_CONFIGFILE_TYPE_LITERAL.getType(); /* * Other constants, sorted alphabetically. */ private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; private static final Logger LOGGER = System.getLogger(OciExtension.class.getName()); // Evaluates to "com.oracle.bmc." (yes, bmc, not oci) as of the current version of the public OCI Java SDK. private static final String OCI_PACKAGE_PREFIX = Service.class.getPackageName() + "."; private static final Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup(); // For any OCI service conceptually named "Example" in an OCI_PACKAGE_PREFIX subpackage named "example": // // Match Strings expected to be class names that start with OCI_PACKAGE_PREFIX... // // (1) ...followed by the service client package fragment ("example")... // (2) ...followed by a period (".")... // (3) ...followed by one or more of the following: // "Example", // "ExampleAsync", // "ExampleAsyncClient", // "ExampleAsyncClientBuilder", // ...bmc.streaming mistakenly doesn't use a nested Builder class; all other services do // "ExampleAsyncClient$Builder", // "ExampleClient", // "ExampleClientBuilder", // "ExampleClient$Builder"... // (4) ...followed by the end of String. // // Capturing group 0: the matched substring ("example.ExampleClientBuilder") // Capturing group 1: Capturing group 2 and base noun ("example.Example") // Capturing group 2: "example" private static final Pattern SERVICE_CLIENT_CLASS_NAME_SUBSTRING_PATTERN = Pattern.compile("^(([^.]+)" // (1) (as many non-periods as possible) + "\\." // (2) (a single period) + ".+?)(?:Async)?(?:Client(?:\\$?Builder)?)?" // (3) + "$"); // (4) /* * Instance fields, sorted alphabetically. */ private final Set additionalVetoes; private boolean lenientClassloading; private final Set serviceTaqs; private final Set unloadableClassNames; /* * Constructors. */ /** * Creates a new {@link OciExtension}. * * @deprecated For {@link java.util.ServiceLoader} use only. */ @Deprecated // for java.util.ServiceLoader use only public OciExtension() { super(); this.lenientClassloading = true; this.serviceTaqs = new HashSet<>(); this.additionalVetoes = new HashSet<>(7); this.unloadableClassNames = new HashSet<>(7); } /* * Container lifecycle observer methods. */ private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery event) { Config c = ConfigProvider.getConfig(); try { this.lenientClassloading = c.getOptionalValue("oci.extension.lenient-classloading", Boolean.class) .orElse(Boolean.TRUE) .booleanValue(); } catch (IllegalArgumentException conversionException) { if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, conversionException.getMessage(), conversionException); } this.lenientClassloading = true; } this.additionalVetoes.addAll(c.getOptionalValue("oci.extension.classname-vetoes", String[].class) .map(Set::of) .orElse(Set.of())); } private void processInjectionPoint(@Observes ProcessInjectionPoint event) { InjectionPoint ip = event.getInjectionPoint(); processInjectionPoint(ip.getAnnotated().getBaseType(), ip.getQualifiers(), event::addDefinitionError); } private void processProviderInjectionPoint(@Observes ProcessInjectionPoint> event) { InjectionPoint ip = event.getInjectionPoint(); processInjectionPoint(((ParameterizedType) ip.getAnnotated().getBaseType()).getActualTypeArguments()[0], ip.getQualifiers(), event::addDefinitionError); } private void afterBeanDiscovery(@Observes AfterBeanDiscovery event, BeanManager bm) { if (!this.serviceTaqs.isEmpty()) { for (ServiceTaqs serviceTaqs : this.serviceTaqs) { Annotation[] qualifiers = serviceTaqs.qualifiers(); if (serviceTaqs.isEmpty()) { installAdpMachinery(event, bm, qualifiers); } else { TypeAndQualifiers serviceClientBuilder = serviceTaqs.serviceClientBuilder(); TypeAndQualifiers serviceClient = serviceTaqs.serviceClient(); installServiceClientBuilder(event, bm, serviceClientBuilder, serviceClient, this.lenientClassloading); installServiceClient(event, bm, serviceClient, serviceTaqs.serviceInterface(), serviceClientBuilder); } } } } private void afterDeploymentValidation(@Observes AfterDeploymentValidation event) { this.unloadableClassNames.clear(); this.additionalVetoes.clear(); this.serviceTaqs.clear(); } /* * Additional instance methods. */ /** * Returns {@code true} if the supplied {@link Class} is known to not be directly related to an Oracle Cloud * Infrastructure service. * *

The check is fast and deliberately not exhaustive.

* * @param c the {@link Class} in question; must not be {@code null}; will be a {@link Class} whose {@linkplain * Class#getPackageName() package name} starts with the value of the {@link #OCI_PACKAGE_PREFIX} field * * @return {@code true} if the supplied {@link Class} is known to not be directly related to an Oracle Cloud * Infrastructure service * * @exception NullPointerException if {@code c} is {@code null} */ private boolean isVetoed(Class c) { // See // https://docs.oracle.com/en-us/iaas/tools/java/latest/overview-summary.html#:~:text=Oracle%20Cloud%20Infrastructure%20Common%20Runtime. // None of these packages contains OCI service clients or service client interfaces or service client // builders. There are other packages (com.oracle.bmc.encryption, as an arbitrary example) that should also // conceptually be vetoed. This method does not currently veto all of them, nor is it clear that it ever could. // The strategy employed here, however, vetoes quite a large number of them correctly and very efficiently // before more sophisticated tests are employed. // // "Veto" in this context means only that this extension will not further process the class in question. The // class remains eligible for further processing; i.e. this is not a CDI veto. if (equals(Service.class.getProtectionDomain(), c.getProtectionDomain()) || this.additionalVetoes.contains(c.getName())) { if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, "Vetoed " + c); } return true; } return false; } private void processInjectionPoint(Type type, Set qualifiers, Consumer errorHandler) { if (!(type instanceof Class)) { // Optimization: all OCI constructs we're interested in are non-generic classes (and not therefore // ParameterizedTypes or GenericArrayTypes). return; } Class c = (Class) type; String className = c.getName(); if (!className.startsWith(OCI_PACKAGE_PREFIX)) { // Optimization: the set of classes we're interested in is a subset of general OCI-related classes. return; } if (AbstractAuthenticationDetailsProvider.class.isAssignableFrom(c) || isAdpBuilderClass(c)) { // Register an "empty" ServiceTaqs as an indicator of demand for some kind of // AbstractAuthenticationDetailsProvider (or a relevant builder). this.serviceTaqs.add(new ServiceTaqs(qualifiers.toArray(EMPTY_ANNOTATION_ARRAY))); return; } Matcher m = SERVICE_CLIENT_CLASS_NAME_SUBSTRING_PATTERN.matcher(className.substring(OCI_PACKAGE_PREFIX.length())); if (!m.matches() || this.isVetoed(c)) { return; } this.processServiceClientInjectionPoint(errorHandler, c, qualifiers, OCI_PACKAGE_PREFIX + m.group(1)); } private void processServiceClientInjectionPoint(Consumer errorHandler, Class c, Set qualifiers, String serviceInterfaceName) { Annotation[] qualifiersArray = qualifiers.toArray(EMPTY_ANNOTATION_ARRAY); ServiceTaqs serviceTaqsForAuth = null; boolean lenient = this.lenientClassloading; // Create types-and-qualifiers for, e.g.: // ....example.Example // ....example.ExampleClient // ....example.ExampleClient$Builder Class serviceInterfaceClass = toClassUnresolved(errorHandler, c, serviceInterfaceName, lenient); if (serviceInterfaceClass != null && serviceInterfaceClass.isInterface()) { String serviceClient = serviceInterfaceName + "Client"; Class serviceClientClass = toClassUnresolved(errorHandler, c, serviceClient, lenient); if (serviceClientClass != null && serviceInterfaceClass.isAssignableFrom(serviceClientClass)) { Class serviceClientBuilderClass = toClassUnresolved(errorHandler, c, serviceClient + "$Builder", true); if (serviceClientBuilderClass == null) { serviceClientBuilderClass = toClassUnresolved(errorHandler, serviceClient + "Builder", lenient); } if (serviceClientBuilderClass != null && ClientBuilderBase.class.isAssignableFrom(serviceClientBuilderClass)) { this.serviceTaqs.add(new ServiceTaqs(qualifiersArray, serviceInterfaceClass, serviceClientClass, serviceClientBuilderClass)); // Use an "empty" ServiceTaqs as an indicator of demand for some kind of // AbstractAuthenticationDetailsProvider (or a relevant builder). serviceTaqsForAuth = new ServiceTaqs(qualifiersArray); this.serviceTaqs.add(serviceTaqsForAuth); } } } // Create types-and-qualifiers for, e.g.: // ....example.ExampleAsync // ....example.ExampleAsyncClient // ....example.ExampleAsyncClient$Builder String serviceAsyncInterface = serviceInterfaceName + "Async"; Class serviceAsyncInterfaceClass = toClassUnresolved(errorHandler, c, serviceAsyncInterface, lenient); if (serviceAsyncInterfaceClass != null && serviceAsyncInterfaceClass.isInterface()) { String serviceAsyncClient = serviceAsyncInterface + "Client"; Class serviceAsyncClientClass = toClassUnresolved(errorHandler, c, serviceAsyncClient, lenient); if (serviceAsyncClientClass != null && serviceAsyncInterfaceClass.isAssignableFrom(serviceAsyncClientClass)) { Class serviceAsyncClientBuilderClass = toClassUnresolved(errorHandler, c, serviceAsyncClient + "$Builder", true); if (serviceAsyncClientBuilderClass == null) { serviceAsyncClientBuilderClass = toClassUnresolved(errorHandler, serviceAsyncClient + "Builder", lenient); } if (serviceAsyncClientBuilderClass != null && ClientBuilderBase.class.isAssignableFrom(serviceAsyncClientBuilderClass)) { this.serviceTaqs.add(new ServiceTaqs(qualifiersArray, serviceAsyncInterfaceClass, serviceAsyncClientClass, serviceAsyncClientBuilderClass)); if (serviceTaqsForAuth == null) { // Use an "empty" ServiceTaqs as an indicator of demand for some kind of // AbstractAuthenticationDetailsProvider (or a relevant builder). this.serviceTaqs.add(new ServiceTaqs(qualifiersArray)); } } } } } private Class toClassUnresolved(Consumer errorHandler, String name, boolean lenient) { return toClassUnresolved(errorHandler, null, name, lenient); } private Class toClassUnresolved(Consumer errorHandler, Class referenceClass, String name, boolean lenient) { if (referenceClass != null && referenceClass.getName().equals(name)) { return referenceClass; } try { return loadClassUnresolved(name); } catch (ClassNotFoundException classNotFoundException) { if (lenient) { if (this.unloadableClassNames.add(name)) { if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, "class " + name + " not found"); } } } else { errorHandler.accept(classNotFoundException); } return null; } } /* * Static methods. */ private static void installAdpMachinery(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // ConfigAccessor installConfigAccessor(event, bm, qualifiers); // Supplier/ConfigFile installConfigFile(event, bm, qualifiers); // ConfigFileAuthenticationDetailsProvider installConfigFileAdp(event, bm, qualifiers); // InstancePrincipalsAuthenticationDetailsProvider installInstancePrincipalsAdp(event, bm, qualifiers); // OkeWorkloadIdentityAuthenticationDetailsProvider installOkeWorkloadIdentityAdp(event, bm, qualifiers); // ResourcePrincipalAuthenticationDetailsProvider installResourcePrincipalAdp(event, bm, qualifiers); // SessionTokenAuthenticationDetailsProvider installSessionTokenAdps(event, bm, qualifiers); // SimpleAuthenticationDetailsProvider installSimpleAdp(event, bm, qualifiers); // CascadingAdpSupplier installCascadingAdp(event, bm, qualifiers); // AbstractAuthenticationDetailsProvider, BasicAuthenticationDetailsProvider installBasicAdp(event, bm, qualifiers); } private static void installConfigAccessor(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // ConfigAccessor if (isUnsatisfied(bm, ConfigAccessor.class, qualifiers)) { event.addBean() .addTransitiveTypeClosure(MicroProfileConfigConfigAccessor.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> produceConfigAccessor(i, qualifiers)); } } private static void installConfigFile(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // Supplier if (isUnsatisfied(bm, SUPPLIER_CONFIGFILE_TYPE, qualifiers)) { event.addBean() .types(SUPPLIER_CONFIGFILE_TYPE) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> ConfigFiles.configFileSupplier(i.select(ConfigAccessor.class, qualifiers).get())); } // ConfigFile if (isUnsatisfied(bm, ConfigFile.class, qualifiers)) { event.addBean() .types(ConfigFile.class) .qualifiers(qualifiers) .scope(Singleton.class) // if this were Dependent, we'd read the file every time .produceWith(i -> i.select(SUPPLIER_CONFIGFILE_TYPE_LITERAL, qualifiers).get()); } } private static void installConfigFileAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AdpSupplier, ConfigFileAdpSupplier if (isUnsatisfied(bm, ADP_SUPPLIER_CONFIG_FILE_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADP_SUPPLIER_CONFIG_FILE_AUTHENTICATION_DETAILS_PROVIDER_TYPE, ConfigFileAdpSupplier.class) .qualifiers(withName(qualifiers, "config-file")) .scope(Singleton.class) .produceWith(i -> new ConfigFileAdpSupplier(i.select(SUPPLIER_CONFIGFILE_TYPE_LITERAL, qualifiers).get())); } // ConfigFileAuthenticationDetailsProvider if (isUnsatisfied(bm, ConfigFileAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(ConfigFileAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> new ConfigFileAuthenticationDetailsProvider(i.select(ConfigFile.class, qualifiers).get())); } } private static void installInstancePrincipalsAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AdpSupplier, InstancePrincipalsAdpSupplier if (isUnsatisfied(bm, ADP_SUPPLIER_INSTANCE_PRINCIPALS_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADP_SUPPLIER_INSTANCE_PRINCIPALS_AUTHENTICATION_DETAILS_PROVIDER_TYPE, InstancePrincipalsAdpSupplier.class) .qualifiers(withName(qualifiers, "instance-principals")) .scope(Singleton.class) .produceWith(i -> new InstancePrincipalsAdpSupplier( i.select(ConfigAccessor.class, qualifiers).get(), i.select(InstancePrincipalsAuthenticationDetailsProviderBuilder.class, qualifiers)::get)); } // InstancePrincipalsAuthenticationDetailsProviderBuilder if (isUnsatisfied(bm, InstancePrincipalsAuthenticationDetailsProviderBuilder.class, qualifiers)) { event.addBean() .types(InstancePrincipalsAuthenticationDetailsProviderBuilder.class) .qualifiers(qualifiers) .scope(Dependent.class) .produceWith(i -> fire(i, InstancePrincipalsAuthenticationDetailsProvider.builder(), qualifiers)); } // InstancePrincipalsAuthenticationDetailsProvider if (isUnsatisfied(bm, InstancePrincipalsAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(InstancePrincipalsAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> i.select(InstancePrincipalsAuthenticationDetailsProviderBuilder.class, qualifiers).get() .build()); } } private static void installOkeWorkloadIdentityAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AdpSupplier, OkeWorkloadIdentityAdpSupplier if (isUnsatisfied(bm, ADPSUPPLIER_OKEWORKLOADIDENTITYAUTHENTICATIONDETAILSPROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADPSUPPLIER_OKEWORKLOADIDENTITYAUTHENTICATIONDETAILSPROVIDER_TYPE, OkeWorkloadIdentityAdpSupplier.class) .qualifiers(withName(qualifiers, "oke-workload-identity")) .scope(Singleton.class) // or Dependent? .produceWith(i -> new OkeWorkloadIdentityAdpSupplier( i.select(OkeWorkloadIdentityAuthenticationDetailsProviderBuilder.class, qualifiers)::get)); } // OkeWorkloadIdentityAuthenticationDetailsProviderBuilder if (isUnsatisfied(bm, OkeWorkloadIdentityAuthenticationDetailsProviderBuilder.class, qualifiers)) { event.addBean() .types(OkeWorkloadIdentityAuthenticationDetailsProviderBuilder.class) .qualifiers(qualifiers) .scope(Dependent.class) .produceWith(i -> fire(i, OkeWorkloadIdentityAuthenticationDetailsProvider.builder(), qualifiers)); } // OkeWorkloadIdentityAuthenticationDetailsProvider if (isUnsatisfied(bm, OkeWorkloadIdentityAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(OkeWorkloadIdentityAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> i.select(OkeWorkloadIdentityAuthenticationDetailsProviderBuilder.class, qualifiers).get() .build()); } } private static void installResourcePrincipalAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AdpSupplier, ResourcePrincipalAdpSupplier if (isUnsatisfied(bm, ADP_SUPPLIER_RESOURCE_PRINCIPAL_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADP_SUPPLIER_RESOURCE_PRINCIPAL_AUTHENTICATION_DETAILS_PROVIDER_TYPE, ResourcePrincipalAdpSupplier.class) .qualifiers(withName(qualifiers, "resource-principal")) .scope(Singleton.class) // or Dependent? .produceWith(i -> new ResourcePrincipalAdpSupplier( i.select(ResourcePrincipalAuthenticationDetailsProviderBuilder.class, qualifiers)::get)); } // ResourcePrincipalAuthenticationDetailsProviderBuilder if (isUnsatisfied(bm, ResourcePrincipalAuthenticationDetailsProviderBuilder.class, qualifiers)) { event.addBean() .types(ResourcePrincipalAuthenticationDetailsProviderBuilder.class) .qualifiers(qualifiers) .scope(Dependent.class) .produceWith(i -> fire(i, ResourcePrincipalAuthenticationDetailsProvider.builder(), qualifiers)); } // ResourcePrincipalAuthenticationDetailsProvider if (isUnsatisfied(bm, ResourcePrincipalAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(ResourcePrincipalAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> i.select(ResourcePrincipalAuthenticationDetailsProviderBuilder.class, qualifiers).get() .build()); } } private static void installSessionTokenAdps(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // SessionTokenAuthenticationDetailsProviderBuilder if (isUnsatisfied(bm, SessionTokenAuthenticationDetailsProviderBuilder.class, qualifiers)) { event.addBean() .types(SessionTokenAuthenticationDetailsProviderBuilder.class) .qualifiers(qualifiers) .scope(Dependent.class) .produceWith(i -> fire(i, SessionTokenAuthenticationDetailsProvider.builder(), qualifiers)); } // AdpSupplier, SessionTokenAdpSupplier (from builder) if (isUnsatisfied(bm, ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE, SessionTokenAdpSupplier.class) .qualifiers(withName(qualifiers, "session-token-builder")) .scope(Singleton.class) .produceWith(i -> SessionTokenAdpSupplier .ofBuilderSupplier(i.select(SessionTokenAuthenticationDetailsProviderBuilder.class, qualifiers)::get)); } Annotation[] qualifiersPlusNamePlusOciConfig; if (qualifiers.length == 0) { qualifiersPlusNamePlusOciConfig = new Annotation[] {OciConfig.Literal.INSTANCE, NamedLiteral.of("session-token-config-file")}; } else { qualifiersPlusNamePlusOciConfig = new Annotation[qualifiers.length + 2]; System.arraycopy(qualifiers, 0, qualifiersPlusNamePlusOciConfig, 0, qualifiers.length); qualifiersPlusNamePlusOciConfig[qualifiers.length] = OciConfig.Literal.INSTANCE; qualifiersPlusNamePlusOciConfig[qualifiers.length + 1] = NamedLiteral.of("session-token-config-file"); } // AdpSupplier, SessionTokenAdpSupplier (from ConfigFile) if (isUnsatisfied(bm, ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiersPlusNamePlusOciConfig)) { event.addBean() .types(ADP_SUPPLIER_SESSION_TOKEN_AUTHENTICATION_DETAILS_PROVIDER_TYPE, SessionTokenAdpSupplier.class) .qualifiers(qualifiersPlusNamePlusOciConfig) .scope(Singleton.class) .produceWith(i -> SessionTokenAdpSupplier.ofConfigFileSupplier(i.select(SUPPLIER_CONFIGFILE_TYPE_LITERAL, qualifiers).get())); } // SessionTokenAuthenticationDetailsProvider (from builder) if (isUnsatisfied(bm, SessionTokenAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(SessionTokenAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> { try { return fire(i, i.select(SessionTokenAuthenticationDetailsProviderBuilder.class, qualifiers).get().build(), qualifiers); } catch (IOException e) { throw new UncheckedIOException(e.getMessage(), e); } }); } // SessionTokenAuthenticationDetailsProvider (from ConfigFile) if (isUnsatisfied(bm, SessionTokenAuthenticationDetailsProvider.class, qualifiersPlusNamePlusOciConfig)) { event.addBean() .types(SessionTokenAuthenticationDetailsProvider.class) .qualifiers(qualifiersPlusNamePlusOciConfig) .scope(Singleton.class) .produceWith(i -> { try { return fire(i, new SessionTokenAuthenticationDetailsProvider(i.select(SUPPLIER_CONFIGFILE_TYPE_LITERAL, qualifiers).get() .get()), qualifiers); } catch (IOException e) { throw new UncheckedIOException(e.getMessage(), e); } }); } } private static void installSimpleAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AdpSupplier, SimpleAdpSupplier if (isUnsatisfied(bm, ADP_SUPPLIER_SIMPLE_AUTHENTICATION_DETAILS_PROVIDER_TYPE, qualifiers)) { event.addBean() .types(ADP_SUPPLIER_SIMPLE_AUTHENTICATION_DETAILS_PROVIDER_TYPE, SimpleAdpSupplier.class) .qualifiers(withName(qualifiers, "config")) .scope(Singleton.class) .produceWith(i -> new SimpleAdpSupplier(i.select(ConfigAccessor.class, qualifiers).get(), i.select(SimpleAuthenticationDetailsProviderBuilder.class, qualifiers)::get)); } // SimpleAuthenticationDetailsProviderBuilder if (isUnsatisfied(bm, SimpleAuthenticationDetailsProviderBuilder.class, qualifiers)) { event.addBean() .types(SimpleAuthenticationDetailsProviderBuilder.class) .qualifiers(qualifiers) .scope(Dependent.class) .produceWith(i -> fire(i, SimpleAuthenticationDetailsProvider.builder(), qualifiers)); } // SimpleAuthenticationDetailsProvider if (isUnsatisfied(bm, SimpleAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(SimpleAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> i.select(SimpleAuthenticationDetailsProviderBuilder.class, qualifiers).get().build()); } } private static void installCascadingAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // CascadingAdpSupplier if (isUnsatisfied(bm, CascadingAdpSupplier.class, qualifiers)) { event.addBean() .addTransitiveTypeClosure(CascadingAdpSupplier.class) .qualifiers(withName(qualifiers, "auto")) .scope(Singleton.class) .produceWith(i -> new CascadingAdpSupplier(n -> adpSupplierFromStrategyDescriptor(i, n, qualifiers), replaceElements(strategyDescriptors(i.select(ConfigAccessor.class, qualifiers).get()), n -> "auto".equals(n) ? DEFAULT_ORDERED_ADP_STRATEGY_DESCRIPTORS : List.of(n)))); } } private static void installBasicAdp(AfterBeanDiscovery event, BeanManager bm, Annotation[] qualifiers) { // AbstractAuthenticationDetailsProvider, BasicAuthenticationDetailsProvider if (isUnsatisfied(bm, BasicAuthenticationDetailsProvider.class, qualifiers)) { event.addBean() .types(AbstractAuthenticationDetailsProvider.class, BasicAuthenticationDetailsProvider.class) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> i.select(ADP_SUPPLIER_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL, qualifiers).get() .get() .orElseThrow(UnsatisfiedResolutionException::new)); } } /** * A method that selects an {@link AdpSupplier AdpSupplier<? extends BasicAuthenticationDetailsProvider>} by * strategy descriptor (and possibly additional qualifiers). * * @param i an {@link Instance}; must not be {@code null} * * @param sd the strategy descriptor; must not be {@code null} * * @param qualifiers additional qualifier {@link Annotation}s; must not be {@code null} * * @return an {@link AdpSupplier AdpSupplier<? extends BasicAuthenticationDetailsProvider>}; never {@code * null} * * @exception NullPointerException if any argument is {@code null} * * @exception UnsatisfiedResolutionException if there is no such {@link AdpSupplier} */ private static AdpSupplier adpSupplierFromStrategyDescriptor(Instance i, String sd, Annotation[] qualifiers) { Instance> adpss = i.select(ADPSUPPLIER_WILDCARD_EXTENDS_BASIC_AUTHENTICATION_DETAILS_PROVIDER_TYPE_LITERAL, withName(qualifiers, sd)); return adpss.isUnsatisfied() ? EmptyAdpSupplier.of() : adpss.get(); } private static boolean installServiceClientBuilder(AfterBeanDiscovery event, BeanManager bm, TypeAndQualifiers serviceClientBuilder, TypeAndQualifiers serviceClient, boolean lenientClassloading) { if (serviceClient == null) { return false; } return installServiceClientBuilder(event, bm, serviceClientBuilder, serviceClient.toClass(), lenientClassloading); } private static boolean installServiceClientBuilder(AfterBeanDiscovery event, BeanManager bm, TypeAndQualifiers serviceClientBuilder, Class serviceClientClass, boolean lenientClassloading) { if (serviceClientBuilder != null && isUnsatisfied(bm, serviceClientBuilder)) { Class serviceClientBuilderClass = serviceClientBuilder.toClass(); MethodHandle builderMethod; try { builderMethod = PUBLIC_LOOKUP.findStatic(serviceClientClass, "builder", methodType(serviceClientBuilderClass)); } catch (IllegalAccessException | NoSuchMethodException reflectiveOperationException) { if (lenientClassloading) { if (LOGGER.isLoggable(WARNING)) { LOGGER.log(WARNING, serviceClientClass.getName() + ".builder() not found"); } } else { event.addDefinitionError(reflectiveOperationException); } return false; } Set types = Set.of(serviceClientBuilderClass); Annotation[] qualifiers = serviceClientBuilder.qualifiers(); event.addBean() .addTransitiveTypeClosure(serviceClientBuilderClass) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> produceClientBuilder(i, builderMethod, qualifiers)); if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, "Added synthetic bean: " + Arrays.toString(qualifiers) + " " + types); } return true; } return false; } private static boolean installServiceClient(AfterBeanDiscovery event, BeanManager bm, TypeAndQualifiers serviceClient, TypeAndQualifiers serviceInterface, TypeAndQualifiers serviceClientBuilder) { if (serviceInterface == null || serviceClientBuilder == null) { return false; } return installServiceClient(event, bm, serviceClient, serviceInterface.type(), serviceClientBuilder.toClass()); } private static boolean installServiceClient(AfterBeanDiscovery event, BeanManager bm, TypeAndQualifiers serviceClient, Type serviceInterfaceType, Class serviceClientBuilderClass) { if (serviceClient != null) { Annotation[] qualifiers = serviceClient.qualifiers(); Type serviceClientType = serviceClient.type(); if (isUnsatisfied(bm, serviceClientType, qualifiers)) { Set types = isUnsatisfied(bm, serviceInterfaceType, qualifiers) ? Set.of(AutoCloseable.class, Object.class, serviceClientType, serviceInterfaceType) : Set.of(AutoCloseable.class, Object.class, serviceClientType); event.addBean() .types(types) .qualifiers(qualifiers) .scope(Singleton.class) .produceWith(i -> produceClient(i, serviceClientBuilderClass, qualifiers)) .disposeWith(OciExtension::disposeClient); return true; } } return false; } private static boolean isAdpBuilderClass(Class c) { return isAdpBuilderClassName(c.getName()); } private static boolean isAdpBuilderClassName(String n) { return n.startsWith(OCI_PACKAGE_PREFIX) && n.endsWith("AuthenticationDetailsProviderBuilder"); } private static MicroProfileConfigConfigAccessor produceConfigAccessor(Instance instance, Annotation[] qualifiers) { Instance configInstance = instance.select(Config.class, qualifiers); if (configInstance.isUnsatisfied()) { configInstance = instance.select(Config.class); } return new MicroProfileConfigConfigAccessor(configInstance.get()); } private static Object produceClientBuilder(Instance instance, MethodHandle builderMethod, Annotation[] qualifiers) { ClientBuilderBase builderInstance; try { builderInstance = (ClientBuilderBase) builderMethod.invoke(); } catch (RuntimeException | Error runtimeExceptionOrError) { throw runtimeExceptionOrError; } catch (Throwable exception) { if (exception instanceof InterruptedException) { Thread.currentThread().interrupt(); } throw new CreationException(exception.getMessage(), exception); } // Permit arbitrary customization. return fire(instance, builderInstance, qualifiers); } private static Object produceClient(Instance instance, Class builderClass, Annotation[] qualifiers) { return ((ClientBuilderBase) instance.select(builderClass, qualifiers).get()) .build(instance.select(AbstractAuthenticationDetailsProvider.class, qualifiers).get()); } private static void disposeClient(Object client, Object ignored) { if (client instanceof AutoCloseable) { close((AutoCloseable) client); } } private static Annotation[] withName(Annotation[] qualifiers, String name) { Annotation[] qualifiersWithName = new Annotation[qualifiers.length + 1]; System.arraycopy(qualifiers, 0, qualifiersWithName, 0, qualifiers.length); qualifiersWithName[qualifiers.length] = NamedLiteral.of(name); return qualifiersWithName; } private static void close(AutoCloseable autoCloseable) { if (autoCloseable != null) { try { autoCloseable.close(); } catch (RuntimeException runtimeException) { throw runtimeException; } catch (Exception exception) { if (exception instanceof InterruptedException) { Thread.currentThread().interrupt(); } throw new IllegalStateException(exception.getMessage(), exception); } } } @SuppressWarnings("unchecked") private static T fire(Instance instance, T payload, Annotation[] qualifiers) { instance.select(EVENT_OBJECT_TYPE_LITERAL).get().select((Class) payload.getClass(), qualifiers).fire(payload); return payload; } private static boolean isUnsatisfied(BeanManager bm, TypeAndQualifiers taq) { return isUnsatisfied(bm, taq.type(), taq.qualifiers()); } private static boolean isUnsatisfied(BeanManager bm, Type type, Annotation[] qualifiers) { Set> beans = bm.getBeans(type, qualifiers); if (beans == null || beans.isEmpty()) { return true; } try { return bm.resolve(beans) == null; } catch (AmbiguousResolutionException ambiguousResolutionException) { if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, ambiguousResolutionException.getMessage(), ambiguousResolutionException); } return false; } } private static boolean equals(ProtectionDomain pd0, ProtectionDomain pd1) { if (pd0 == null) { return pd1 == null; } else if (pd1 == null) { return false; } return equals(pd0.getCodeSource(), pd1.getCodeSource()); } private static boolean equals(CodeSource cs0, CodeSource cs1) { if (cs0 == null) { return cs1 == null; } else if (cs1 == null) { return false; } return equals(cs0.getLocation(), cs1.getLocation()); } private static boolean equals(URL url0, URL url1) { if (url0 == null) { return url1 == null; } else if (url1 == null) { return false; } try { return Objects.equals(url0.toURI(), url1.toURI()); } catch (URISyntaxException uriSyntaxException) { if (LOGGER.isLoggable(DEBUG)) { LOGGER.log(DEBUG, uriSyntaxException.getMessage(), uriSyntaxException); } // Use URL#equals(Object) only as a last resort, since it involves DNS lookups (!). return url0.equals(url1); } } private static Class loadClassUnresolved(String name) throws ClassNotFoundException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return Class.forName(name, false, cl == null ? OciExtension.class.getClassLoader() : cl); } /* * Inner and nested classes. */ private record TypeAndQualifiers(Type type, Annotation[] qualifiers) { private TypeAndQualifiers { Objects.requireNonNull(type, "type"); qualifiers = qualifiers == null ? EMPTY_ANNOTATION_ARRAY : qualifiers.clone(); } private Class toClass() { Type t = this.type(); if (t instanceof Class c) { return c; } else if (t instanceof ParameterizedType pt) { return (Class) pt.getRawType(); } else { return null; } } // Record's implicit equals() Overridden to ensure Arrays.equals() is used on qualifiers. @Override // Record public boolean equals(Object other) { if (other == this) { return true; } else if (other != null && other.getClass() == this.getClass()) { TypeAndQualifiers her = (TypeAndQualifiers) other; return Objects.equals(this.type(), her.type()) && Arrays.equals(this.qualifiers(), her.qualifiers()); } else { return false; } } // Record's implicit hashCode() Overridden to ensure Arrays.hashCode() is used on qualifiers. @Override // Record public int hashCode() { int hashCode = 17; int c = this.type().hashCode(); hashCode = 37 * hashCode + c; c = Arrays.hashCode(this.qualifiers()); hashCode = 37 * hashCode + c; return hashCode; } @Override // Record public String toString() { return Arrays.asList(this.qualifiers()).toString() + " " + this.type().toString(); } } private record ServiceTaqs(Annotation[] qualifiers, Type serviceInterfaceType, Type serviceClientType, Type serviceClientBuilderType) { private ServiceTaqs { if (qualifiers == null) { qualifiers = EMPTY_ANNOTATION_ARRAY; } } private ServiceTaqs(Annotation[] qualifiers) { this(qualifiers, null, null, null); } private TypeAndQualifiers serviceInterface() { return new TypeAndQualifiers(this.serviceInterfaceType(), this.qualifiers()); } private TypeAndQualifiers serviceClient() { return new TypeAndQualifiers(this.serviceClientType(), this.qualifiers()); } private TypeAndQualifiers serviceClientBuilder() { return new TypeAndQualifiers(this.serviceClientBuilderType(), this.qualifiers()); } private boolean isEmpty() { return this.serviceInterfaceType() == null && this.serviceClientType() == null && this.serviceClientBuilderType() == null; } // Record's implicit equals() overridden to ensure Arrays.equals() is used on qualifiers. @Override // Record public boolean equals(Object other) { if (other == this) { return true; } else if (other != null && other.getClass() == this.getClass()) { ServiceTaqs her = (ServiceTaqs) other; return Arrays.equals(this.qualifiers(), her.qualifiers()) && Objects.equals(this.serviceInterfaceType(), her.serviceInterfaceType()) && Objects.equals(this.serviceClientType(), her.serviceClientType()) && Objects.equals(this.serviceClientBuilderType(), her.serviceClientBuilderType()); } else { return false; } } // Record's implicit equals() overridden to ensure Arrays.hashCode() is used on qualifiers. @Override // Record public int hashCode() { int hashCode = 17; Annotation[] qualifiers = this.qualifiers(); int c = qualifiers == null ? 0 : Arrays.hashCode(qualifiers); hashCode = 37 * hashCode + c; Type x = this.serviceInterfaceType(); c = x == null ? 0 : x.hashCode(); hashCode = 37 * hashCode + c; x = this.serviceClientType(); c = x == null ? 0 : x.hashCode(); hashCode = 37 * hashCode + c; x = this.serviceClientBuilderType(); c = x == null ? 0 : x.hashCode(); hashCode = 37 * hashCode + c; return hashCode; } } }