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.proxy;
017
018import static java.lang.ClassLoader.getSystemClassLoader;
019import static java.util.Arrays.asList;
020import static java.util.function.Function.identity;
021import static java.util.stream.Collectors.toMap;
022
023import java.io.Externalizable;
024import java.io.ObjectStreamException;
025import java.io.Serializable;
026import java.lang.reflect.InvocationHandler;
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029import java.lang.reflect.Proxy;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032import java.util.stream.Stream;
033
034import org.talend.sdk.component.runtime.reflect.Defaults;
035
036import lombok.AllArgsConstructor;
037import lombok.RequiredArgsConstructor;
038
039@AllArgsConstructor
040public class JavaProxyEnricherFactory {
041
042    public Object asSerializable(final ClassLoader loader, final String plugin, final String key,
043            final Object instanceToWrap) {
044        return this.asSerializable(loader, plugin, key, instanceToWrap, false);
045    }
046
047    public Object asSerializable(final ClassLoader loader, final String plugin, final String key,
048            final Object instanceToWrap, final boolean force) {
049        final Class<?>[] interfaces = instanceToWrap.getClass().getInterfaces();
050        final boolean isSerializable =
051                Stream.of(interfaces).anyMatch(i -> i == Serializable.class || i == Externalizable.class);
052        if ((!force) && isSerializable && !instanceToWrap.getClass().getName().startsWith("org.apache.johnzon.core.")) {
053            return instanceToWrap;
054        }
055        final Class[] api = isSerializable ? interfaces
056                : Stream.concat(Stream.of(Serializable.class), Stream.of(interfaces)).toArray(Class[]::new);
057        return Proxy
058                .newProxyInstance(selectLoader(api, loader), api,
059                        new DelegatingSerializableHandler(instanceToWrap, plugin, key));
060    }
061
062    private ClassLoader selectLoader(final Class[] api, final ClassLoader loader) {
063        if (Stream.of(api).anyMatch(t -> t.getClassLoader() == loader) || loader.getParent() == null
064                || loader == getSystemClassLoader()) {
065            return loader;
066        }
067        final ClassLoader parent = loader.getParent();
068        if (parent == null) {
069            return getSystemClassLoader();
070        }
071        for (final Class<?> test : api) {
072            try {
073                parent.loadClass(test.getName());
074            } catch (final ClassNotFoundException e) {
075                for (final Class<?> test2 : api) {
076                    try {
077                        loader.loadClass(test2.getName());
078                    } catch (final ClassNotFoundException ex) {
079                        throw new IllegalStateException("No matching classloader for " + asList(api) + ":\n"
080                                + "Current: " + loader + "\n" + "Parent: " + parent + "\n" + "API: "
081                                + Stream.of(api).collect(toMap(identity(), Class::getClassLoader)) + "\n");
082                    }
083                }
084                return loader;
085            }
086        }
087        return parent;
088    }
089
090    @RequiredArgsConstructor
091    private static class DelegatingSerializableHandler implements InvocationHandler, Serializable {
092
093        private final Object delegate;
094
095        private final String plugin;
096
097        private final String key;
098
099        private final ConcurrentMap<Method, Boolean> defaultMethods = new ConcurrentHashMap<>();
100
101        @Override
102        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
103            if (Defaults.isDefaultAndShouldHandle(method) && defaultMethods.computeIfAbsent(method, m -> {
104                try {
105                    delegate.getClass().getMethod(method.getName(), method.getParameterTypes());
106                    return false;
107                } catch (final NoSuchMethodException e) {
108                    return true;
109                }
110            })) {
111                return Defaults.handleDefault(method.getDeclaringClass(), method, proxy, args);
112            }
113
114            if (Object.class == method.getDeclaringClass()) {
115                switch (method.getName()) {
116                case "equals":
117                    return args != null && args.length == 1 && method.getDeclaringClass().isInstance(args[0])
118                            && Proxy.isProxyClass(args[0].getClass()) && (this == Proxy.getInvocationHandler(args[0])
119                                    || delegate == Proxy.getInvocationHandler(args[0]));
120                default:
121                }
122            }
123            try {
124                return method.invoke(delegate, args);
125            } catch (final InvocationTargetException ite) {
126                throw ite.getTargetException();
127            }
128        }
129
130        Object writeReplace() throws ObjectStreamException {
131            return new SerializationHandlerReplacer(plugin, key);
132        }
133    }
134}