Skip to content

Commit b9468ed

Browse files
committed
#87 - Make sure models are serializable
1 parent f25b10a commit b9468ed

24 files changed

+751
-11
lines changed

hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexClassDetails.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.List;
1010

1111
import org.hibernate.models.internal.ClassDetailsSupport;
12+
import org.hibernate.models.internal.SerialCassDetails;
13+
import org.hibernate.models.internal.jdk.SerialJdkCassDetails;
1214
import org.hibernate.models.internal.util.CollectionHelper;
1315
import org.hibernate.models.spi.ClassDetails;
1416
import org.hibernate.models.spi.FieldDetails;
@@ -276,4 +278,10 @@ private static List<TypeVariableDetails> determineTypeParameters(ClassInfo class
276278
}
277279
return result;
278280
}
281+
282+
@Override
283+
public SerialCassDetails toSerialForm(SourceModelBuildingContext context) {
284+
final Class<Object> classForName = context.getClassLoading().classForName( getClassName() );
285+
return new SerialJdkCassDetails( classForName.getName(), classForName );
286+
}
279287
}

hibernate-models-jandex/src/main/java/org/hibernate/models/jandex/internal/JandexModelBuildingContextImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static org.hibernate.models.internal.ModelsClassLogging.MODELS_CLASS_LOGGER;
2323

2424
/**
25+
* SourceModelBuildingContext implementation based on Jandex
26+
*
2527
* @author Steve Ebersole
2628
*/
2729
public class JandexModelBuildingContextImpl extends AbstractModelBuildingContext implements JandexModelBuildingContext {
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.io.ObjectInputStream;
12+
import java.io.ObjectOutputStream;
13+
import java.io.ObjectStreamClass;
14+
import java.io.OutputStream;
15+
import java.io.Serializable;
16+
17+
/**
18+
* Assists with the serialization process and performs additional
19+
* functionality based on serialization.
20+
* <p>
21+
* <ul>
22+
* <li>Deep clone using serialization
23+
* <li>Serialize managing finally and IOException
24+
* <li>Deserialize managing finally and IOException
25+
* </ul>
26+
* <p>
27+
* This class throws exceptions for invalid {@code null} inputs.
28+
*
29+
* @author Nissim Karpenstein
30+
* @author Janek Bogucki
31+
* @author Daniel Rall
32+
* @author Stephen Colebourne
33+
* @author Jeff Varszegi
34+
* @author Gary Gregory
35+
*
36+
* @since 1.0
37+
*/
38+
public final class SerializationHelper {
39+
private SerializationHelper() {
40+
}
41+
42+
// Clone
43+
//-----------------------------------------------------------------------
44+
45+
/**
46+
* Deep clone an object using serialization.
47+
* <p>
48+
* This is many times slower than writing clone methods by hand
49+
* on all objects in your object graph. However, for complex object
50+
* graphs, or for those that don't support deep cloning this can
51+
* be a simple alternative implementation. Of course all the objects
52+
* must be {@code Serializable}.
53+
*
54+
* @param object the {@code Serializable} object to clone
55+
*
56+
* @return the cloned object
57+
*/
58+
public static <T extends Serializable> T clone(T object) {
59+
if ( object == null ) {
60+
return null;
61+
}
62+
return deserialize( serialize( object ), object.getClass().getClassLoader() );
63+
}
64+
65+
// Serialize
66+
//-----------------------------------------------------------------------
67+
68+
/**
69+
* <p>Serializes an object to the given stream.
70+
* <p>
71+
* The stream will be closed once the object is written.
72+
* This avoids the need for a finally clause, and maybe also
73+
* for exception handling, in the application code.
74+
* <p>
75+
* The stream passed in is not buffered internally within this
76+
* method. This is the responsibility of the caller, if desired.
77+
*
78+
* @param obj the object to serialize to bytes, may be null
79+
* @param outputStream the stream to write to, must not be null
80+
*
81+
* @throws IllegalArgumentException if {@code outputStream} is null
82+
*/
83+
public static void serialize(Serializable obj, OutputStream outputStream) {
84+
if ( outputStream == null ) {
85+
throw new IllegalArgumentException( "The OutputStream must not be null" );
86+
}
87+
88+
ObjectOutputStream out = null;
89+
try {
90+
// stream closed in the finally
91+
out = new ObjectOutputStream( outputStream );
92+
out.writeObject( obj );
93+
94+
}
95+
catch (IOException ex) {
96+
throw new RuntimeException( "could not serialize", ex );
97+
}
98+
finally {
99+
try {
100+
if ( out != null ) {
101+
out.close();
102+
}
103+
}
104+
catch (IOException ignored) {
105+
}
106+
}
107+
}
108+
109+
/**
110+
* Serializes an object to a byte array for storage or
111+
* externalization.
112+
*
113+
* @param obj the object to serialize to bytes
114+
*
115+
* @return a byte[] with the converted Serializable
116+
*/
117+
public static byte[] serialize(Serializable obj) {
118+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( 512 );
119+
serialize( obj, byteArrayOutputStream );
120+
return byteArrayOutputStream.toByteArray();
121+
}
122+
123+
// Deserialize
124+
//-----------------------------------------------------------------------
125+
126+
/**
127+
* Deserializes an object from the given stream using the
128+
* Thread Context ClassLoader (TCCL).
129+
* <p>
130+
* Delegates to {@link #doDeserialize}
131+
*
132+
* @param inputStream the serialized object input stream, must not be null
133+
*
134+
* @return the deserialized object
135+
*
136+
* @throws IllegalArgumentException if {@code inputStream} is null
137+
*/
138+
public static <T extends Serializable> T deserialize(InputStream inputStream) {
139+
return doDeserialize( inputStream, defaultClassLoader(), hibernateClassLoader(), null );
140+
}
141+
142+
/**
143+
* Returns the Thread Context ClassLoader (TCCL).
144+
*
145+
* @return The current TCCL
146+
*/
147+
public static ClassLoader defaultClassLoader() {
148+
return Thread.currentThread().getContextClassLoader();
149+
}
150+
151+
public static ClassLoader hibernateClassLoader() {
152+
return SerializationHelper.class.getClassLoader();
153+
}
154+
155+
/**
156+
* Deserializes an object from the given stream using the
157+
* Thread Context ClassLoader (TCCL). If there is no TCCL set,
158+
* the classloader of the calling class is used.
159+
* <p>
160+
* The stream will be closed once the object is read. This
161+
* avoids the need for a finally clause, and maybe also for
162+
* exception handling, in the application code.
163+
* <p>
164+
* The stream passed in is not buffered internally within this
165+
* method. This is the responsibility of the caller, if desired.
166+
*
167+
* @param inputStream the serialized object input stream, must not be null
168+
* @param loader The classloader to use
169+
*
170+
* @return the deserialized object
171+
*
172+
* @throws IllegalArgumentException if <code>inputStream</code> is <code>null</code>
173+
*/
174+
public static Object deserialize(InputStream inputStream, ClassLoader loader) {
175+
return doDeserialize( inputStream, loader, defaultClassLoader(), hibernateClassLoader() );
176+
}
177+
178+
@SuppressWarnings("unchecked")
179+
public static <T> T doDeserialize(
180+
InputStream inputStream,
181+
ClassLoader loader,
182+
ClassLoader fallbackLoader1,
183+
ClassLoader fallbackLoader2) {
184+
if ( inputStream == null ) {
185+
throw new IllegalArgumentException( "The InputStream must not be null" );
186+
}
187+
188+
try {
189+
CustomObjectInputStream in = new CustomObjectInputStream(
190+
inputStream,
191+
loader,
192+
fallbackLoader1,
193+
fallbackLoader2
194+
);
195+
try {
196+
return (T) in.readObject();
197+
}
198+
catch (ClassNotFoundException | IOException e) {
199+
throw new RuntimeException( "could not deserialize", e );
200+
}
201+
finally {
202+
try {
203+
in.close();
204+
}
205+
catch (IOException ignore) {
206+
// ignore
207+
}
208+
}
209+
}
210+
catch (IOException e) {
211+
throw new RuntimeException( "could not deserialize", e );
212+
}
213+
}
214+
215+
/**
216+
* Deserializes an object from an array of bytes using the
217+
* Thread Context ClassLoader (TCCL). If there is no TCCL set,
218+
* the classloader of the calling class is used.
219+
* <p>
220+
* Delegates to {@link #deserialize(byte[], ClassLoader)}
221+
*
222+
* @param objectData the serialized object, must not be null
223+
*
224+
* @return the deserialized object
225+
*
226+
* @throws IllegalArgumentException if <code>objectData</code> is <code>null</code>
227+
*/
228+
public static <T extends Serializable> T deserialize(byte[] objectData) {
229+
return doDeserialize( wrap( objectData ), defaultClassLoader(), hibernateClassLoader(), null );
230+
}
231+
232+
private static InputStream wrap(byte[] objectData) {
233+
if ( objectData == null ) {
234+
throw new IllegalArgumentException( "The byte[] must not be null" );
235+
}
236+
return new ByteArrayInputStream( objectData );
237+
}
238+
239+
/**
240+
* Deserializes an object from an array of bytes.
241+
* <p>
242+
* Delegates to {@link #deserialize(InputStream, ClassLoader)} using a
243+
* {@link ByteArrayInputStream} to wrap the array.
244+
*
245+
* @param objectData the serialized object, must not be null
246+
* @param loader The classloader to use
247+
*
248+
* @return the deserialized object
249+
*
250+
* @throws IllegalArgumentException if <code>objectData</code> is <code>null</code>
251+
*/
252+
public static <T extends Serializable> T deserialize(byte[] objectData, ClassLoader loader) {
253+
return doDeserialize( wrap( objectData ), loader, defaultClassLoader(), hibernateClassLoader() );
254+
}
255+
256+
257+
/**
258+
* By default, to resolve the classes being deserialized JDK serialization uses the
259+
* classes loader which loaded the class which initiated the deserialization call. Here
260+
* that would be Hibernate classes. However, there are cases where that is not the correct
261+
* class loader to use; mainly here we are worried about deserializing user classes in
262+
* environments (app servers, etc) where Hibernate is on a parent classes loader. To
263+
* facilitate for that we allow passing in the class loader we should use.
264+
*/
265+
private static final class CustomObjectInputStream extends ObjectInputStream {
266+
private final ClassLoader loader1;
267+
private final ClassLoader loader2;
268+
private final ClassLoader loader3;
269+
270+
private CustomObjectInputStream(
271+
InputStream in,
272+
ClassLoader loader1,
273+
ClassLoader loader2,
274+
ClassLoader loader3) throws IOException {
275+
super( in );
276+
this.loader1 = loader1;
277+
this.loader2 = loader2;
278+
this.loader3 = loader3;
279+
}
280+
281+
@Override
282+
protected Class resolveClass(ObjectStreamClass v) throws IOException, ClassNotFoundException {
283+
final String className = v.getName();
284+
285+
try {
286+
return Class.forName( className, false, loader1 );
287+
}
288+
catch (ClassNotFoundException ignored) {
289+
}
290+
291+
if ( different( loader1, loader2 ) ) {
292+
try {
293+
return Class.forName( className, false, loader2 );
294+
}
295+
catch (ClassNotFoundException ignored) {
296+
}
297+
}
298+
299+
if ( different( loader1, loader3 ) && different( loader2, loader3 ) ) {
300+
try {
301+
return Class.forName( className, false, loader3 );
302+
}
303+
catch (ClassNotFoundException ignored) {
304+
}
305+
}
306+
307+
// By default delegate to normal JDK deserialization which will use the class loader
308+
// of the class which is calling this deserialization.
309+
return super.resolveClass( v );
310+
}
311+
312+
private boolean different(ClassLoader one, ClassLoader other) {
313+
if ( one == null ) {
314+
return other != null;
315+
}
316+
return !one.equals( other );
317+
}
318+
}
319+
}

hibernate-models/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ plugins {
22
id "published-java-module"
33
}
44

5-
description = "A de-typed abstraction over reflection and annotations"
5+
description = "A de-typed abstraction over reflection and annotations"
6+
7+
dependencies {
8+
testImplementation project( ":hibernate-models-testing" )
9+
}

0 commit comments

Comments
 (0)