001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.portal.restclient;
017
018import static de.cuioss.portal.configuration.impl.producer.ConnectionMetadataProducer.MISSING_BASENAME_MSG;
019import static de.cuioss.tools.collect.CollectionLiterals.mutableList;
020import static de.cuioss.tools.lang.MoreObjects.requireType;
021import static de.cuioss.tools.string.MoreStrings.requireNotEmpty;
022
023import java.io.Closeable;
024import java.lang.reflect.ParameterizedType;
025import java.lang.reflect.Type;
026import java.util.List;
027import java.util.Optional;
028
029import jakarta.enterprise.context.ApplicationScoped;
030import jakarta.enterprise.context.Dependent;
031import jakarta.enterprise.inject.Produces;
032import jakarta.enterprise.inject.spi.InjectionPoint;
033
034import de.cuioss.portal.configuration.impl.producer.ConnectionMetadataProducer;
035import de.cuioss.portal.configuration.util.ConfigurationHelper;
036import de.cuioss.tools.base.Preconditions;
037import de.cuioss.tools.logging.CuiLogger;
038
039/**
040 * Produces a {@link RestClientHolder} to the given service interface.
041 */
042@ApplicationScoped
043public class RestClientProducer {
044
045    private static final CuiLogger log = new CuiLogger(RestClientProducer.class);
046
047    @Produces
048    @Dependent
049    @PortalRestClient(baseName = "unused")
050    <T extends Closeable> RestClientHolder<T> produceRestClient(final InjectionPoint injectionPoint) {
051        final var annotationMetaData = ConfigurationHelper.resolveAnnotation(injectionPoint, PortalRestClient.class)
052                .orElseThrow(() -> new IllegalArgumentException(
053                        "Expected injectionPoint annotated with @PortalRestClient, but was not:" + injectionPoint));
054
055        var type = requireType(injectionPoint.getType(), ParameterizedType.class);
056        List<Type> arguments = mutableList(type.getActualTypeArguments());
057        Preconditions.checkArgument(!arguments.isEmpty());
058
059        @SuppressWarnings("unchecked")
060        var serviceInterface = (Class<T>) arguments.get(0);
061
062        // Basename must be present
063        final var baseName = suffixNameWithDot(requireNotEmpty(annotationMetaData.baseName(), MISSING_BASENAME_MSG));
064
065        log.debug("Producing DsmlClient for baseName ='{}'", baseName);
066
067        final var failOnInvalidConfiguration = annotationMetaData.failOnInvalidConfiguration();
068        try {
069            var connectionMetadata = ConnectionMetadataProducer.createConnectionMetadata(baseName,
070                    failOnInvalidConfiguration);
071            return new RestClientHolder<>(new CuiRestClientBuilder(resolveCuiLogger(injectionPoint, serviceInterface))
072                    .connectionMetadata(connectionMetadata).build(serviceInterface));
073        } catch (IllegalArgumentException e) {
074            log.error("Initialization of RestClientHolder failed", e);
075            return new RestClientHolder<>(null);
076        }
077    }
078
079    /**
080     * @param name to be suffixed, must not be null
081     * @return the given name suffixed with a dot
082     */
083    public static String suffixNameWithDot(final String name) {
084        return name.endsWith(".") ? name : name + ".";
085    }
086
087    private Optional<Class<?>> resolveCallerClass(InjectionPoint ip) {
088        if (null != ip && null != ip.getMember() && null != ip.getMember().getDeclaringClass()) {
089
090            // works only due to @Dependent scope injection point!
091            final Class<?> clazz = ip.getMember().getDeclaringClass();
092            if (clazz.getName().contains("$Proxy$")) { // checking for the sake of a peaceful mind
093                log.debug("not using class {} as it seems to be a Weld CDI proxy", clazz.getName());
094                return Optional.empty();
095            }
096            return Optional.of(clazz);
097        }
098        return Optional.empty();
099    }
100
101    private CuiLogger resolveCuiLogger(InjectionPoint ip, Class<?> fallback) {
102        final var clazz = resolveCallerClass(ip).orElse(fallback);
103        log.debug("Using logger class: {}", clazz.getName());
104        return new CuiLogger(clazz);
105    }
106}