Skip to content

Commit 8d6068a

Browse files
committed
Add serialVersionUID tolerant java deserializer
Workaround for spring-projects#3737
1 parent 9759950 commit 8d6068a

File tree

5 files changed

+253
-0
lines changed

5 files changed

+253
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.support.serializer;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
22+
import org.springframework.core.NestedIOException;
23+
import org.springframework.core.serializer.Deserializer;
24+
import org.springframework.util.LinkedMultiValueMap;
25+
import org.springframework.util.MultiValueMap;
26+
27+
/**
28+
* A {@link Deserializer} implementation that reads an input stream using Java serialization in a
29+
* {@link VersionTolerantObjectInputStream serialVersionUID tolerant} manner.
30+
*
31+
* <pre>
32+
* <code>
33+
* VersionTolerantJavaDeserializer deser = new VersionTolerantJavaDeserializer();
34+
* deser.allowSerialVersionUID(MessageHistory.class, 1426799817181873282L);
35+
* deser.allowSerialVersionUID(MessageHistory.class, -2340400235574314134L);
36+
*
37+
* JdbcMessageStore messageStore = ...
38+
* messageStore.setDeserializer(deser);
39+
* Message<Foo> oldFoo = messageStore.getMessage(oldFooMsgUid);
40+
* </code>
41+
* </pre>
42+
*
43+
* @author Chris Bono
44+
*/
45+
public class VersionTolerantJavaDeserializer implements Deserializer<Object> {
46+
47+
private final MultiValueMap<Class, Long> alternativeSerialVersionUIDs = new LinkedMultiValueMap<>();
48+
49+
/**
50+
* Allows an alternative {@code serialVersionUID} to be used when deserializing a class.
51+
* @param clazz the class
52+
* @param serialVersionUID the alternate uid to allow
53+
*/
54+
public void allowSerialVersionUID(Class clazz, long serialVersionUID) {
55+
alternativeSerialVersionUIDs.add(clazz, serialVersionUID);
56+
}
57+
58+
@Override
59+
public Object deserialize(InputStream inputStream) throws IOException {
60+
VersionTolerantObjectInputStream objectInputStream = new VersionTolerantObjectInputStream(inputStream, this.alternativeSerialVersionUIDs);
61+
try {
62+
return objectInputStream.readObject();
63+
}
64+
catch (ClassNotFoundException ex) {
65+
throw new NestedIOException("Failed to deserialize object type", ex);
66+
}
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.support.serializer;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.ObjectInputStream;
22+
import java.io.ObjectStreamClass;
23+
import java.io.Serializable;
24+
25+
import org.springframework.util.MultiValueMap;
26+
27+
/**
28+
* Custom {@link ObjectInputStream} that allows {@link Serializable} classes to be deserialized with more
29+
* than one {@code serialVersionUID}.
30+
*
31+
* @author Chris Bono
32+
*/
33+
public class VersionTolerantObjectInputStream extends ObjectInputStream {
34+
35+
private final MultiValueMap<Class, Long> alternativeSerialVersionUIDs;
36+
37+
public VersionTolerantObjectInputStream(InputStream in, MultiValueMap<Class, Long> alternativeSerialVersionUIDs) throws IOException {
38+
super(in);
39+
this.alternativeSerialVersionUIDs = alternativeSerialVersionUIDs;
40+
}
41+
42+
@Override
43+
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
44+
45+
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
46+
47+
String className = resultClassDescriptor.getName();
48+
Class localClass = Class.forName(className);
49+
if (!this.alternativeSerialVersionUIDs.containsKey(localClass)) {
50+
return resultClassDescriptor;
51+
}
52+
53+
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
54+
if (localClassDescriptor == null) {
55+
return resultClassDescriptor;
56+
}
57+
58+
final long localSerialVersionUID = localClassDescriptor.getSerialVersionUID();
59+
final long streamSerialVersionUID = resultClassDescriptor.getSerialVersionUID();
60+
if (streamSerialVersionUID != localSerialVersionUID && this.alternativeSerialVersionUIDs.get(localClass).contains(streamSerialVersionUID)) {
61+
return localClassDescriptor;
62+
}
63+
64+
return resultClassDescriptor;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.support.serializer;
18+
19+
import java.io.FileInputStream;
20+
import java.io.FileOutputStream;
21+
import java.io.InvalidClassException;
22+
import java.io.ObjectInputStream;
23+
import java.io.ObjectOutputStream;
24+
import java.io.Serializable;
25+
26+
import org.junit.jupiter.api.Nested;
27+
import org.junit.jupiter.api.Test;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
31+
32+
/**
33+
* Unit tests for {@link VersionTolerantJavaDeserializer}.
34+
*/
35+
public class VersionTolerantJavaDeserializerTests {
36+
37+
@Test
38+
void failsWhenNoAlternateUIDsConfigured() throws Exception {
39+
assertThat(Foo.serialVersionUID).isEqualTo(3L);
40+
String fileName = "./src/test/resources/deser/foo2";
41+
VersionTolerantJavaDeserializer deser = new VersionTolerantJavaDeserializer();
42+
try (FileInputStream fis = new FileInputStream(fileName)) {
43+
assertThatThrownBy(() -> deser.deserialize(fis))
44+
.isInstanceOf(InvalidClassException.class);
45+
}
46+
}
47+
48+
@Test
49+
void passesWhenAlternateUIDsConfigured() throws Exception {
50+
assertThat(Foo.serialVersionUID).isEqualTo(3L);
51+
52+
VersionTolerantJavaDeserializer deser = new VersionTolerantJavaDeserializer();
53+
deser.allowSerialVersionUID(VersionTolerantJavaDeserializerTests.Foo.class, 1L);
54+
deser.allowSerialVersionUID(VersionTolerantJavaDeserializerTests.Foo.class, 2L);
55+
56+
String fileName = "./src/test/resources/deser/foo1";
57+
try (FileInputStream fis = new FileInputStream(fileName)) {
58+
VersionTolerantJavaDeserializerTests.Foo oldFoo = (VersionTolerantJavaDeserializerTests.Foo) deser.deserialize(fis);
59+
assertThat(oldFoo.toString()).isEqualTo("Foo (name=uno, serialVersionUID=3)");
60+
}
61+
62+
fileName = "./src/test/resources/deser/foo2";
63+
try (FileInputStream fis = new FileInputStream(fileName)) {
64+
VersionTolerantJavaDeserializerTests.Foo oldFoo = (VersionTolerantJavaDeserializerTests.Foo) deser.deserialize(fis);
65+
assertThat(oldFoo.toString()).isEqualTo("Foo (name=dos, serialVersionUID=3)");
66+
}
67+
}
68+
69+
@Nested
70+
class GenerateSerializedFiles {
71+
72+
@Test
73+
void generateOldFoo1SerializedFile() throws Exception {
74+
// Only run this once to generate the Foo1 serialized file - make sure Foo.serialVersionUID = 1L before running
75+
assertThat(Foo.serialVersionUID).isEqualTo(1L);
76+
String filename = "./src/test/resources/deser/foo1";
77+
doGenerateOldFooSerializedFile(filename, new Foo("uno"));
78+
}
79+
80+
@Test
81+
void generateOldFoo2SerializedFile() throws Exception {
82+
// Only run this once to generate the Foo2 serialized file - make sure Foo.serialVersionUID = 2L before running
83+
assertThat(Foo.serialVersionUID).isEqualTo(2L);
84+
String filename = "./src/test/resources/deser/foo2";
85+
doGenerateOldFooSerializedFile(filename, new Foo("dos"));
86+
}
87+
88+
private void doGenerateOldFooSerializedFile(String filename, Foo foo) throws Exception {
89+
FileOutputStream fos = new FileOutputStream(filename);
90+
ObjectOutputStream oos = new ObjectOutputStream(fos);
91+
oos.writeObject(foo);
92+
fos.close();
93+
FileInputStream fis = new FileInputStream(filename);
94+
ObjectInputStream ois = new ObjectInputStream(fis);
95+
Foo fooDeser = (Foo) ois.readObject();
96+
assertThat(fooDeser.getName()).isEqualTo(foo.getName());
97+
fis.close();
98+
}
99+
}
100+
101+
static class Foo implements Serializable {
102+
103+
private static final long serialVersionUID = 3L;
104+
105+
private String name;
106+
107+
public Foo(String name) {
108+
this.name = name;
109+
}
110+
111+
public String getName() {
112+
return this.name;
113+
}
114+
115+
public String toString() {
116+
return String.format("Foo (name=%s, serialVersionUID=%d)", this.name, serialVersionUID);
117+
}
118+
}
119+
}
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)