001/**
002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com
003 *
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 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
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 org.talend.sdk.component.runtime.manager.interceptor;
017
018import static java.util.Comparator.comparing;
019import static java.util.stream.Collectors.toList;
020
021import java.lang.annotation.Annotation;
022import java.lang.reflect.Constructor;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.util.Collection;
026import java.util.List;
027import java.util.Map;
028import java.util.Optional;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentMap;
031import java.util.function.BiFunction;
032import java.util.stream.Stream;
033
034import org.talend.sdk.component.api.service.cache.Cached;
035import org.talend.sdk.component.api.service.cache.LocalCache;
036import org.talend.sdk.component.api.service.interceptor.InterceptorHandler;
037import org.talend.sdk.component.api.service.interceptor.Intercepts;
038
039import lombok.AllArgsConstructor;
040import lombok.Getter;
041
042@AllArgsConstructor
043public class InterceptorHandlerFacade implements InterceptorHandler {
044
045    @Getter
046    private final Object delegate;
047
048    private final Map<Class<?>, Object> services;
049
050    private final ConcurrentMap<Method, BiFunction<Method, Object[], Object>> invocations = new ConcurrentHashMap<>();
051
052    @Override
053    public Object invoke(final Method method, final Object[] args) {
054        return invocations.computeIfAbsent(method, m -> {
055            final Collection<InvokerHandler> handlers = Stream
056                    .of(method.getAnnotations())
057                    .filter(a -> interceptsConfig(a) != null)
058                    .sorted(comparing(a -> interceptsConfig(a).priority()))
059                    .map(a -> Optional
060                            .of(interceptsConfig(a).value())
061                            .filter(v -> v != InterceptorHandler.class)
062                            .map(handler -> {
063                                Optional<Constructor<?>> constructor = findConstructor(handler, BiFunction.class);
064                                if (constructor.isPresent()) {
065                                    return new InvokerHandler(constructor.get(), true, null);
066                                }
067                                constructor = findConstructor(handler, Object.class);
068                                if (constructor.isPresent()) { // any, assume all params are services
069                                    return new InvokerHandler(constructor.get(), false, null);
070                                }
071                                constructor = findConstructor(handler, null);
072                                if (constructor.isPresent()) {
073                                    return new InvokerHandler(constructor.get(), false, null);
074                                }
075                                throw new IllegalArgumentException("No available constructor for " + handler);
076                            })
077                            .map(InvokerHandler.class::cast)
078                            .orElseGet(() -> { // built-in interceptors
079                                if (a.annotationType() == Cached.class) {
080                                    try {
081                                        return new InvokerHandler(
082                                                CacheHandler.class.getConstructor(BiFunction.class, LocalCache.class),
083                                                true, null);
084                                    } catch (final NoSuchMethodException e) {
085                                        throw new IllegalStateException("Bad classpath", e);
086                                    }
087                                }
088                                throw new IllegalArgumentException("No handler for " + a);
089                            }))
090                    .collect(toList());
091            if (handlers.isEmpty()) {
092                return (mtd, arguments) -> doInvoke(method, args);
093            }
094
095            // init all InvokerHandler
096            final List<InvokerHandler> invokerHandlers =
097                    handlers.stream().filter(i -> i.invoker).map(InvokerHandler.class::cast).collect(toList());
098            if (invokerHandlers.isEmpty() && handlers.size() > 1) {
099                throw new IllegalArgumentException("Interceptors not compatible for " + m + ": "
100                        + handlers.stream().filter(i -> !invokerHandlers.contains(i)).collect(toList()));
101            }
102            if (invokerHandlers.isEmpty()) {
103                return handlers.iterator().next()::invoke;
104            }
105
106            if (invokerHandlers.size() != handlers.size()) {
107                throw new IllegalArgumentException("Some handlers don't take an invoker as parameter for method " + m
108                        + ": " + handlers.stream().filter(i -> !invokerHandlers.contains(i)).collect(toList()));
109            }
110            for (int i = 0; i < invokerHandlers.size(); i++) {
111                final InvokerHandler invokerHandler = invokerHandlers.get(i);
112                invokerHandler
113                        .init(i == invokerHandlers.size() - 1 ? this::doInvoke : invokerHandlers.get(i + 1)::invoke,
114                                delegate, services);
115            }
116            return invokerHandlers.get(0)::invoke;
117        }).apply(method, args);
118    }
119
120    private Intercepts interceptsConfig(final Annotation a) {
121        return a.annotationType().getAnnotation(Intercepts.class);
122    }
123
124    private Optional<Constructor<?>> findConstructor(final Class<? extends InterceptorHandler> handler,
125            final Class<?> firstParamType) {
126        return Stream
127                .of(handler.getConstructors())
128                .filter(c -> firstParamType == null
129                        || (c.getParameterCount() > 0 && c.getParameterTypes()[0] == firstParamType))
130                .findFirst();
131    }
132
133    private Object doInvoke(final Method method, final Object[] args) {
134        try {
135            return method.invoke(delegate, args);
136        } catch (final InvocationTargetException ite) {
137            final Throwable cause = ite.getCause();
138            if (RuntimeException.class.isInstance(cause)) {
139                throw RuntimeException.class.cast(cause);
140            }
141            throw new IllegalStateException(cause.getMessage());
142        } catch (final IllegalAccessException e) {
143            throw new IllegalStateException(e);
144        }
145    }
146
147    @AllArgsConstructor
148    public static class InvokerHandler implements InterceptorHandler {
149
150        private final Constructor<?> constructor;
151
152        private final boolean invoker;
153
154        private InterceptorHandler delegate;
155
156        @Override
157        public Object invoke(final Method method, final Object[] args) {
158            return delegate.invoke(method, args);
159        }
160
161        private void init(final BiFunction<Method, Object[], Object> invoker, final Object delegate,
162                final Map<Class<?>, Object> services) {
163            try {
164                final Object[] args = buildArgs(invoker, delegate, services);
165                this.delegate = InterceptorHandler.class.cast(constructor.newInstance(args));
166            } catch (final InstantiationException | IllegalAccessException e) {
167                throw new IllegalStateException(e);
168            } catch (final InvocationTargetException e) {
169                throw new IllegalStateException(e.getCause());
170            }
171        }
172
173        private Object[] buildArgs(final BiFunction<Method, Object[], Object> invoker, final Object delegate,
174                final Map<Class<?>, Object> services) {
175            final Object[] args = new Object[constructor.getParameterCount()];
176            for (int i = 0; i < constructor.getParameterCount(); i++) {
177                final Class<?> type = constructor.getParameterTypes()[i];
178                if (BiFunction.class == type) {
179                    args[i] = invoker;
180                } else if (Object.class == type) {
181                    args[i] = delegate;
182                } else {
183                    args[i] = services.get(type);
184                }
185            }
186            return args;
187        }
188    }
189}