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

nl.vpro.api.client.pages.PageUpdateApiClient Maven / Gradle / Ivy

There is a newer version: 8.3.0
Show newest version
package nl.vpro.api.client.pages;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Stream;

import javax.crypto.SecretKey;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;

import com.google.common.base.Suppliers;

import nl.vpro.api.client.resteasy.AbstractApiClient;
import nl.vpro.api.client.utils.Config;
import nl.vpro.api.client.utils.Swagger;
import nl.vpro.domain.classification.CachedURLClassificationServiceImpl;
import nl.vpro.domain.classification.ClassificationService;
import nl.vpro.domain.page.PageIdMatch;
import nl.vpro.domain.page.update.PageUpdate;
import nl.vpro.jmx.MBeans;
import nl.vpro.rs.client.VersionResult;
import nl.vpro.rs.converters.ConditionalBasicAuthentication;
import nl.vpro.rs.pages.update.PageUpdateRestService;
import nl.vpro.rs.provider.ApiProviderRestService;
import nl.vpro.rs.thesaurus.update.ThesaurusUpdateRestService;
import nl.vpro.util.*;

import static nl.vpro.api.client.utils.Config.CONFIG_FILE;
import static nl.vpro.api.client.utils.Config.URLS_FILE;

@Slf4j
public class PageUpdateApiClient extends AbstractApiClient {

    private PageUpdateRestService pageUpdateRestService;

    private ApiProviderRestService apiProvider;

    private ThesaurusUpdateRestService thesaurusUpdateRestService;

    @Getter
    private final String description;

    private final String jwsIssuer;
    private final byte[] jwsKey;
    private final String jwsUser;

    private final ConditionalBasicAuthentication authentication;


    private ClassificationService classificationService;

    @SuppressWarnings({"SpringAutowiredFieldsWarningInspection", "OptionalUsedAsFieldOrParameterType"})
    @Named
    public static class Provider implements javax.inject.Provider {

        @Inject
        @Named("npo-pages_publisher.baseUrl")
        String baseUrl;
        @Inject
        @Named("npo-pages_publisher.user")
        String user;
        @Inject
        @Named("npo-pages_publisher.password")
        String password;

        @Inject
        @Named("npo-pages_publisher.jwsIssuer")
        Optional jwsIssuer;
        @Inject
        @Named("npo-pages_publisher.jwsKey")
        Optional jwsKey;
        @Inject
        @Named("npo-pages_publisher.jwsUser")
        Optional jwsUser;

        @Inject
        @Named("npo-pages_publisher.connectionRequestTimeout")
        Optional connectionRequestTimeout;
        @Inject
        @Named("npo-pages_publisher.connectTimeout")
        Optional connectTimeout;
        @Inject
        @Named("npo-pages_publisher.socketTimeout")
        Optional socketTimeout;
        @Inject
        @Named("npo-pages_publisher.maxConnections")
        Optional maxConnections;
        @Inject
        @Named("npo-pages_publisher.maxConnectionsPerRoute")
        Optional maxConnectionsPerRoute;
        @Inject
        @Named("npo-pages_publisher.warnThreshold")
        Optional warnThreshold;


        @Inject
        @Named("npo-pages_publisher.connectionInPoolTTL")
        Optional connectionInPoolTTL;

        @Inject
        @Named("npo-pages_publisher.validateAfterInactivity")
        Optional validateAfterInactivity;

        // should have worked, but at least I couldn't get it working in magnolia. I made a duration convertor in ProviderAndBuilder now.
        // Optional warnThreshold;

        @Inject
        private Optional classificationService;

        @Override
        public PageUpdateApiClient get() {
            return ProviderAndBuilder.fillAndCatch(this, builder()).build();
        }
    }

    public static class Builder {

    }

    @lombok.Builder(builderClassName = "Builder")
    protected PageUpdateApiClient(
        String baseUrl,
        Duration connectionRequestTimeout,
        Duration connectTimeout,
        Duration socketTimeout,
        Integer maxConnections,
        Integer maxConnectionsPerRoute,
        Duration connectionInPoolTTL,
        Duration validateAfterInactivity,
        Duration countWindow,
        Integer bucketCount,
        Duration warnThreshold,
        List acceptableLanguages,
        MediaType accept,
        Boolean trustAll,
        String user,
        String password,
        String mbeanName,
        ClassificationService classificationService,
        String jwsIssuer,
        String jwsKey,
        String jwsUser,
        ClassLoader classLoader,
        String userAgent,
        Boolean registerMBean,
        boolean eager
        ) {
        super(baseUrl == null ? "https://publish.pages.omroep.nl/api" : (baseUrl + (baseUrl.endsWith("/") ?  "" : "/") + "api"),
            connectionRequestTimeout,
            connectTimeout,
            socketTimeout,
            maxConnections,
            maxConnectionsPerRoute,
            null,
            null,
            connectionInPoolTTL,
            validateAfterInactivity,
            countWindow,
            bucketCount,
            warnThreshold,
            acceptableLanguages,
            accept,
            null,
            trustAll,
            null,
            mbeanName,
            classLoader,
            userAgent,
            registerMBean,
            eager
        );
        if (user == null){
            throw new IllegalArgumentException("No user given");
        }
        if (password == null) {
            throw new IllegalArgumentException("No  password given");
        }
        authentication = new ConditionalBasicAuthentication(user, password);
        description = user + "@" + this.getBaseUrl();
        this.classificationService = classificationService;
        this.jwsIssuer = jwsIssuer;
        this.jwsKey = jwsKey == null ? null : jwsKey.getBytes();
        this.jwsUser = jwsUser;

    }

    @Override
    protected Stream> services() {
        return Stream.of(
            this::getPageUpdateRestService,
            this::getThesaurusUpdateRestService,
            this::getClassificationService,
            this::getProviderRestService
        );
    }

    @Override
    protected void appendTestResult(StringBuilder builder, String arg) {
        super.appendTestResult(builder, arg);
        try {
            String url = MBeans.isBlank(arg) ? "http://www-acc.2doc.nl/documentaires/series/2doc/2016/maart/paradijs-glaswater0.html" : arg;
            PageUpdate load = getPageUpdateRestService().load(url, true, PageIdMatch.BOTH);
            builder.append("load(").append(url).append(") = ").append(load);
        } catch (Exception e) {
            builder.append("Could not load ").append(arg).append(": ").append(e.getMessage());
        }
    }

    public static Builder configured(String... configFiles) {
        Builder builder = builder();
        log.info("Reading configuration from {}", Arrays.asList(configFiles));
        ConfigUtils.configured(builder, configFiles);
        return builder;
    }

    public static Builder configured(Env env, String... configFiles) {
        Builder  builder = builder();
        ConfigUtils.configured(env, builder, configFiles);
        return builder;
    }

    public static Builder configured(Env env, Map settings) {
        Builder  builder = builder();
        ConfigUtils.configured(env, builder, settings);
        return builder;
    }


    public static Builder configured(Env env) {
        Builder builder = builder();
        Config config = new Config(URLS_FILE, CONFIG_FILE);
        config.setEnv(env);
        ReflectionUtils.configured(builder, config.getProperties(Config.Prefix.pages_publisher));
        return builder;
    }

    public static Builder configured() {
        return configured((Env) null);
    }


    public PageUpdateRestService getPageUpdateRestService() {
        return pageUpdateRestService = produceIfNull(
            () -> pageUpdateRestService,
            () -> proxyErrorsAndCount(
                PageUpdateRestService.class,
                    getTarget(getClientHttpEngine())
                        .proxyBuilder(PageUpdateRestService.class)
                        .classloader(classLoader)
                        .defaultConsumes(MediaType.APPLICATION_XML)
                        .build(),
                Error.class
            ));
    }


    public ApiProviderRestService getProviderRestService() {
        return apiProvider = produceIfNull(
            () -> apiProvider,
            () -> proxyErrorsAndCount(
                ApiProviderRestService.class,
                    getTarget(getClientHttpEngine())
                        .proxyBuilder(ApiProviderRestService.class)
                        .classloader(classLoader)
                        .defaultConsumes(MediaType.APPLICATION_XML)
                        .build(),
                Error.class
            ));
    }


    public ThesaurusUpdateRestService getThesaurusUpdateRestService() {
        return thesaurusUpdateRestService = produceIfNull(
            () -> thesaurusUpdateRestService,
            () -> proxyErrorsAndCount(ThesaurusUpdateRestService.class,
                proxyForJws(
                    getTarget(getClientHttpEngine())
                        .proxyBuilder(ThesaurusUpdateRestService.class)
                        .classloader(classLoader)
                        .defaultConsumes(MediaType.APPLICATION_XML)
                        .build()
                ),
                Error.class
            ));
    }


    public ThesaurusUpdateRestService proxyForJws(ThesaurusUpdateRestService clean) {
        return (ThesaurusUpdateRestService) Proxy.newProxyInstance(
            classLoader,
            new Class[]{ThesaurusUpdateRestService.class}, new JwsAspect(clean)
        );
    }



    public ClassificationService getClassificationService() {
        return classificationService = produceIfNull(
            () -> classificationService,
            () -> {
                String classificationUrl = this.baseUrl.replaceAll("/api$", "");

                ClassificationService impl = new CachedURLClassificationServiceImpl(classificationUrl);
                log.info("No classification service wired. Created {}", impl);
                return impl;
            });
    }

    @Override
    public String toString() {
        return getClass().getName() + " " + getDescription();
    }

    @Override
    protected void buildResteasy(ResteasyClientBuilder builder) {
        builder
            .register(authentication);
    }


    @Override
    public synchronized void invalidate() {
        super.invalidate();
        pageUpdateRestService = null;
        thesaurusUpdateRestService = null;
    }

    private Supplier version = null;

    public String getVersion() {
        if (version == null) {
            version = Suppliers.memoizeWithExpiration(() -> Swagger.getVersionFromSwagger(baseUrl, "5.8"), 30, TimeUnit.MINUTES);
        }
        return version.get().getVersion();
    }

    public boolean isAvailable() {
        return version.get().isAvailable();
    }

    public IntegerVersion getVersionNumber() {
        return Swagger.getVersionNumber(getVersion());
    }


    protected String jws(String subject) {
        Instant now = Instant.now();
        Instant expires = now.plus(Duration.ofHours(12));
        SecretKey secretKey = Keys.hmacShaKeyFor(jwsKey);
        String compactJws = Jwts.builder()
            .setSubject(subject)
            .claim("usr", jwsUser)
            .setIssuedAt(Date.from(now))
            .setIssuer(jwsIssuer)
            .setExpiration(Date.from(expires))
            .signWith(secretKey)
            .compact();
        log.debug(compactJws);
        return compactJws;
    }

    protected class JwsAspect implements InvocationHandler  {

        private final ThesaurusUpdateRestService proxied;

        public JwsAspect(ThesaurusUpdateRestService proxied) {
            this.proxied = proxied;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (args != null) {

                Annotation[][] annotations = method.getParameterAnnotations();
                for (int i = 0; i < annotations.length; i++) {
                    for (Annotation o : annotations[i]) {
                        if (o instanceof HeaderParam headerParam) {
                            if (headerParam.value().equals(HttpHeaders.AUTHORIZATION)) {
                                args[i] = ThesaurusUpdateRestService.AUTHENTICATION_SCHEME + " " + jws(method.getName());
                            }
                        }
                    }
                }
            }
            return method.invoke(proxied, args);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy