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}