1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.executor.loader;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.Externalizable;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InvalidClassException;
24 import java.io.ObjectInput;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutput;
27 import java.io.ObjectOutputStream;
28 import java.io.ObjectStreamClass;
29 import java.io.ObjectStreamException;
30 import java.io.StreamCorruptedException;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.apache.ibatis.reflection.factory.ObjectFactory;
37
38
39
40
41
42 public abstract class AbstractSerialStateHolder implements Externalizable {
43
44 private static final long serialVersionUID = 8940388717901644661L;
45 private static final ThreadLocal<ObjectOutputStream> stream = new ThreadLocal<>();
46 private byte[] userBeanBytes = new byte[0];
47 private Object userBean;
48 private Map<String, ResultLoaderMap.LoadPair> unloadedProperties;
49 private ObjectFactory objectFactory;
50 private Class<?>[] constructorArgTypes;
51 private Object[] constructorArgs;
52
53 public AbstractSerialStateHolder() {
54 }
55
56 public AbstractSerialStateHolder(
57 final Object userBean,
58 final Map<String, ResultLoaderMap.LoadPair> unloadedProperties,
59 final ObjectFactory objectFactory,
60 List<Class<?>> constructorArgTypes,
61 List<Object> constructorArgs) {
62 this.userBean = userBean;
63 this.unloadedProperties = new HashMap<>(unloadedProperties);
64 this.objectFactory = objectFactory;
65 this.constructorArgTypes = constructorArgTypes.toArray(new Class<?>[0]);
66 this.constructorArgs = constructorArgs.toArray(new Object[0]);
67 }
68
69 @Override
70 public final void writeExternal(final ObjectOutput out) throws IOException {
71 boolean firstRound = false;
72 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
73 ObjectOutputStream os = stream.get();
74 if (os == null) {
75 os = new ObjectOutputStream(baos);
76 firstRound = true;
77 stream.set(os);
78 }
79
80 os.writeObject(this.userBean);
81 os.writeObject(this.unloadedProperties);
82 os.writeObject(this.objectFactory);
83 os.writeObject(this.constructorArgTypes);
84 os.writeObject(this.constructorArgs);
85
86 final byte[] bytes = baos.toByteArray();
87 out.writeObject(bytes);
88
89 if (firstRound) {
90 stream.remove();
91 }
92 }
93
94 @Override
95 public final void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
96 final Object data = in.readObject();
97 if (data.getClass().isArray()) {
98 this.userBeanBytes = (byte[]) data;
99 } else {
100 this.userBean = data;
101 }
102 }
103
104 @SuppressWarnings("unchecked")
105 protected final Object readResolve() throws ObjectStreamException {
106
107 if (this.userBean != null && this.userBeanBytes.length == 0) {
108 return this.userBean;
109 }
110
111
112 try (ObjectInputStream in = new LookAheadObjectInputStream(new ByteArrayInputStream(this.userBeanBytes))) {
113 this.userBean = in.readObject();
114 this.unloadedProperties = (Map<String, ResultLoaderMap.LoadPair>) in.readObject();
115 this.objectFactory = (ObjectFactory) in.readObject();
116 this.constructorArgTypes = (Class<?>[]) in.readObject();
117 this.constructorArgs = (Object[]) in.readObject();
118 } catch (final IOException ex) {
119 throw (ObjectStreamException) new StreamCorruptedException().initCause(ex);
120 } catch (final ClassNotFoundException ex) {
121 throw (ObjectStreamException) new InvalidClassException(ex.getLocalizedMessage()).initCause(ex);
122 }
123
124 final Map<String, ResultLoaderMap.LoadPair> arrayProps = new HashMap<>(this.unloadedProperties);
125 final List<Class<?>> arrayTypes = Arrays.asList(this.constructorArgTypes);
126 final List<Object> arrayValues = Arrays.asList(this.constructorArgs);
127
128 return this.createDeserializationProxy(userBean, arrayProps, objectFactory, arrayTypes, arrayValues);
129 }
130
131 protected abstract Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
132 List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
133
134 private static class LookAheadObjectInputStream extends ObjectInputStream {
135 private static final List<String> blacklist = Arrays.asList(
136 "org.apache.commons.beanutils.BeanComparator",
137 "org.apache.commons.collections.functors.InvokerTransformer",
138 "org.apache.commons.collections.functors.InstantiateTransformer",
139 "org.apache.commons.collections4.functors.InvokerTransformer",
140 "org.apache.commons.collections4.functors.InstantiateTransformer",
141 "org.codehaus.groovy.runtime.ConvertedClosure",
142 "org.codehaus.groovy.runtime.MethodClosure",
143 "org.springframework.beans.factory.ObjectFactory",
144 "org.springframework.transaction.jta.JtaTransactionManager",
145 "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
146
147 public LookAheadObjectInputStream(InputStream in) throws IOException {
148 super(in);
149 }
150
151 @Override
152 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
153 String className = desc.getName();
154 if (blacklist.contains(className)) {
155 throw new InvalidClassException(className, "Deserialization is not allowed for security reasons. "
156 + "It is strongly recommended to configure the deserialization filter provided by JDK. "
157 + "See http://openjdk.java.net/jeps/290 for the details.");
158 }
159 return super.resolveClass(desc);
160 }
161 }
162 }