1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.lang3;
19
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InvalidObjectException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.io.ObjectStreamClass;
28 import java.io.OutputStream;
29 import java.io.Serializable;
30 import java.util.Objects;
31
32 /**
33 * Performs additional functionality for serialization.
34 *
35 * <ul>
36 * <li>Deep clone using serialization</li>
37 * <li>Serialize managing finally and IOException</li>
38 * <li>Deserialize managing finally and IOException</li>
39 * </ul>
40 *
41 * <p>
42 * This class throws exceptions for invalid {@code null} inputs. Each method documents its behavior in more detail.
43 * </p>
44 * <p>
45 * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
46 * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
47 * </p>
48 * <p>
49 * #ThreadSafe#
50 * </p>
51 *
52 * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
53 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
54 * @since 1.0
55 */
56 public class SerializationUtils {
57
58 /**
59 * Custom specialization of the standard JDK {@link ObjectInputStream} that uses a custom {@link ClassLoader} to resolve a class. If the specified
60 * {@link ClassLoader} is not able to resolve the class, the context classloader of the current thread will be used. This way, the standard deserialization
61 * also works in web application containers and application servers, regardless of which {@link ClassLoader} loaded the class that encapsulates
62 * serialization/deserialization.
63 *
64 * <p>
65 * For more in-depth information about the problem for which this class here is a workaround, see the JIRA issue LANG-626.
66 * </p>
67 */
68 static final class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
69
70 private final ClassLoader classLoader;
71
72 /**
73 * Constructs a new instance.
74 *
75 * @param in The {@link InputStream}.
76 * @param classLoader classloader to use
77 * @throws IOException Thrown if an I/O error occurs while reading stream header.
78 * @see java.io.ObjectInputStream
79 */
80 ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
81 super(in);
82 this.classLoader = classLoader;
83 }
84
85 /**
86 * Overridden version that uses the parameterized {@link ClassLoader} or the {@link ClassLoader} of the current {@link Thread} to resolve the class.
87 *
88 * @param desc An instance of class {@link ObjectStreamClass}.
89 * @return A {@link Class} object corresponding to {@code desc}.
90 * @throws IOException Thrown if an I/O error occurs.
91 * @throws ClassNotFoundException Thrown if class of a serialized object cannot be found.
92 */
93 @Override
94 protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
95 final String name = desc.getName();
96 try {
97 return Class.forName(name, false, classLoader);
98 } catch (final ClassNotFoundException ex) {
99 try {
100 return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
101 } catch (final ClassNotFoundException cnfe) {
102 final Class<?> cls = ClassUtils.getPrimitiveClass(name);
103 if (cls != null) {
104 return cls;
105 }
106 throw cnfe;
107 }
108 }
109 }
110 }
111
112 /**
113 * Deep clones an {@link Object} using serialization.
114 *
115 * <p>
116 * This is many times slower than writing clone methods by hand on all objects in your object graph. However, for complex object graphs, or for those that
117 * don't support deep cloning this can be a simple alternative implementation. Of course all the objects must be {@link Serializable}.
118 * </p>
119 *
120 * @param <T> the type of the object involved.
121 * @param object The {@link Serializable} object to clone.
122 * @return The cloned object.
123 * @throws SerializationException Thrown if the serialization fails.
124 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
125 */
126 public static <T extends Serializable> T clone(final T object) {
127 if (object == null) {
128 return null;
129 }
130 final ByteArrayInputStream bais = new ByteArrayInputStream(serialize(object));
131 final Class<T> cls = ObjectUtils.getClass(object);
132 try (ClassLoaderAwareObjectInputStream in = new ClassLoaderAwareObjectInputStream(bais, cls.getClassLoader())) {
133 // When we serialize and deserialize an object, it is reasonable to assume the deserialized object is of the
134 // same type as the original serialized object
135 return (T) in.readObject();
136 } catch (final ClassNotFoundException | IOException ex) {
137 throw new SerializationException(String.format("%s while reading cloned object data", ex.getClass().getSimpleName()), ex);
138 }
139 }
140
141 /**
142 * Deserializes a single {@link Object} from an array of bytes.
143 *
144 * <p>
145 * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. Without Generics in this declaration, the
146 * call site must type cast and can cause the same ClassCastException. Note that in both cases, the ClassCastException is in the call site, not in this
147 * method.
148 * </p>
149 * <p>
150 * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
151 * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
152 * </p>
153 *
154 * @param <T> the object type to be deserialized.
155 * @param objectData The serialized object, must not be null.
156 * @return The deserialized object.
157 * @throws NullPointerException Thrown if {@code objectData} is {@code null}.
158 * @throws SerializationException Thrown if the serialization fails.
159 * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
160 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
161 */
162 public static <T> T deserialize(final byte[] objectData) {
163 Objects.requireNonNull(objectData, "objectData");
164 return deserialize(new ByteArrayInputStream(objectData));
165 }
166
167 /**
168 * Deserializes an {@link Object} from the specified stream.
169 *
170 * <p>
171 * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also exception handling, in the application
172 * code.
173 * </p>
174 *
175 * <p>
176 * The stream passed in is not buffered internally within this method. This is the responsibility of your application if desired.
177 * </p>
178 *
179 * <p>
180 * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. Without Generics in this declaration, the
181 * call site must type cast and can cause the same ClassCastException. Note that in both cases, the ClassCastException is in the call site, not in this
182 * method.
183 * </p>
184 *
185 * <p>
186 * If you want to secure deserialization with a whitelist or blacklist, please use Apache Commons IO's
187 * {@link org.apache.commons.io.serialization.ValidatingObjectInputStream ValidatingObjectInputStream}.
188 * </p>
189 *
190 * @param <T> the object type to be deserialized.
191 * @param inputStream The serialized object input stream, must not be null.
192 * @return The deserialized object.
193 * @throws NullPointerException Thrown if {@code inputStream} is {@code null}.
194 * @throws SerializationException Thrown if the serialization fails.
195 * @see org.apache.commons.io.serialization.ValidatingObjectInputStream
196 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
197 */
198 @SuppressWarnings("resource") // inputStream is managed by the caller
199 public static <T> T deserialize(final InputStream inputStream) {
200 Objects.requireNonNull(inputStream, "inputStream");
201 try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
202 @SuppressWarnings("unchecked")
203 final T obj = (T) in.readObject();
204 return obj;
205 } catch (final ClassNotFoundException | IOException | NegativeArraySizeException ex) {
206 throw new SerializationException(ex);
207 }
208 }
209
210 /**
211 * Checks that the specified object reference is not {@code null} and throws a customized {@link InvalidObjectException} if it is. This method is designed
212 * primarily for doing state validation in {@link Serializable} class's {@code readObject(ObjectInputStream)} methods.
213 *
214 * @param obj The object reference to check for nullity.
215 * @param message detail message to be used in the event that a {@link InvalidObjectException} is thrown.
216 * @param <T> the type of the reference.
217 * @return {@code obj} if not {@code null}.
218 * @throws InvalidObjectException Thrown if {@code obj} is {@code null}.
219 * @see Serializable
220 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
221 * @since 3.21.0
222 */
223 public static <T> T requireNonNull(final T obj, final String message) throws InvalidObjectException {
224 if (obj == null) {
225 throw new InvalidObjectException(message);
226 }
227 return obj;
228 }
229
230 /**
231 * Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that implement {@link Serializable}.
232 *
233 * @param <T> The type of the object involved.
234 * @param obj The object to roundtrip.
235 * @return The serialized and deserialized object.
236 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
237 * @since 3.3
238 */
239 @SuppressWarnings("unchecked") // OK, because we serialized a type `T`
240 public static <T extends Serializable> T roundtrip(final T obj) {
241 return (T) deserialize(serialize(obj));
242 }
243
244 /**
245 * Serializes an {@link Object} to a byte array for storage/serialization.
246 *
247 * @param obj The object to serialize to bytes.
248 * @return A byte[] with the converted Serializable.
249 * @throws SerializationException Thrown if the serialization fails.
250 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
251 */
252 public static byte[] serialize(final Serializable obj) {
253 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
254 serialize(obj, baos);
255 return baos.toByteArray();
256 }
257
258 /**
259 * Serializes an {@link Object} to the specified stream.
260 *
261 * <p>
262 * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also exception handling, in the application
263 * code.
264 * </p>
265 *
266 * <p>
267 * The stream passed in is not buffered internally within this method. This is the responsibility of your application if desired.
268 * </p>
269 *
270 * @param obj The object to serialize to bytes, may be null.
271 * @param outputStream The stream to write to, must not be null.
272 * @throws NullPointerException Thrown if {@code outputStream} is {@code null}.
273 * @throws SerializationException Thrown if the serialization fails.
274 * @see <a href="https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/">Java Object Serialization Specification</a>
275 */
276 @SuppressWarnings("resource") // outputStream is managed by the caller
277 public static void serialize(final Serializable obj, final OutputStream outputStream) {
278 Objects.requireNonNull(outputStream, "outputStream");
279 try (ObjectOutputStream out = new ObjectOutputStream(outputStream)) {
280 out.writeObject(obj);
281 } catch (final IOException ex) {
282 throw new SerializationException(ex);
283 }
284 }
285
286 /**
287 * SerializationUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
288 * {@code SerializationUtils.clone(object)}.
289 *
290 * <p>
291 * This constructor is public to permit tools that require a JavaBean instance to operate.
292 * </p>
293 *
294 * @since 2.0
295 * @deprecated TODO Make private in 4.0.
296 */
297 @Deprecated
298 public SerializationUtils() {
299 // empty
300 }
301 }