001/** 002 * Copyright (C) 2006-2024 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}