-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add optional Hazelcast session serializer #1671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eleftherias
merged 7 commits into
spring-projects:master
from
enozcan:hz-stream-serializer
Sep 8, 2020
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
910b782
Add serializer.
enozcan 4619338
Add javadoc, use new serializer for tests.
enozcan 5e7ca6d
Add serializer config to docs.
enozcan c5a0bd2
Use random id, fix javadoc.
enozcan e2f5d08
Add test.
enozcan c8d73a3
Fix checkstyle.
enozcan f1a832e
Add since tag.
enozcan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
...lcast/src/main/java/org/springframework/session/hazelcast/HazelcastSessionSerializer.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* | ||
* Copyright 2014-2020 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.session.hazelcast; | ||
|
||
import java.io.EOFException; | ||
import java.io.IOException; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
|
||
import com.hazelcast.nio.ObjectDataInput; | ||
import com.hazelcast.nio.ObjectDataOutput; | ||
import com.hazelcast.nio.serialization.StreamSerializer; | ||
|
||
import org.springframework.session.MapSession; | ||
|
||
/** | ||
* A {@link com.hazelcast.nio.serialization.Serializer} implementation that handles the | ||
* (de)serialization of {@link MapSession} stored on {@link com.hazelcast.core.IMap}. | ||
* | ||
* <p> | ||
* The use of this serializer is optional and provides faster serialization of sessions. | ||
* If not configured to be used, Hazelcast will serialize sessions via | ||
* {@link java.io.Serializable} by default. | ||
* | ||
* <p> | ||
* If multiple instances of a Spring application is run, then all of them need to use the | ||
* same serialization method. If this serializer is registered on one instance and not | ||
* another one, then it will end up with HazelcastSerializationException. The same applies | ||
* when clients are configured to use this serializer but not the members, and vice versa. | ||
* Also note that, if a new instance is created with this serialization but the existing | ||
* Hazelcast cluster contains the values not serialized by this but instead the default | ||
* one, this will result in incompatibility again. | ||
* | ||
* <p> | ||
* An example of how to register the serializer on embedded instance can be seen below: | ||
* | ||
* <pre class="code"> | ||
* Config config = new Config(); | ||
* | ||
* // ... other configurations for Hazelcast ... | ||
* | ||
* SerializerConfig serializerConfig = new SerializerConfig(); | ||
* serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class); | ||
* config.getSerializationConfig().addSerializerConfig(serializerConfig); | ||
* | ||
* HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); | ||
* </pre> | ||
* | ||
* Below is the example of how to register the serializer on client instance. Note that, | ||
* to use the serializer in client/server mode, the serializer - and hence | ||
* {@link MapSession}, must exist on the server's classpath and must be registered via | ||
* {@link com.hazelcast.config.SerializerConfig} with the configuration above for each | ||
* server. | ||
* | ||
* <pre class="code"> | ||
* ClientConfig clientConfig = new ClientConfig(); | ||
* | ||
* // ... other configurations for Hazelcast Client ... | ||
* | ||
* SerializerConfig serializerConfig = new SerializerConfig(); | ||
* serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class); | ||
* clientConfig.getSerializationConfig().addSerializerConfig(serializerConfig); | ||
* | ||
* HazelcastInstance hazelcastClient = HazelcastClient.newHazelcastClient(clientConfig); | ||
* </pre> | ||
* | ||
* @author Enes Ozcan | ||
* @since 2.4.0 | ||
*/ | ||
public class HazelcastSessionSerializer implements StreamSerializer<MapSession> { | ||
|
||
private static final int SERIALIZER_TYPE_ID = 1453; | ||
|
||
@Override | ||
public void write(ObjectDataOutput out, MapSession session) throws IOException { | ||
out.writeUTF(session.getOriginalId()); | ||
out.writeUTF(session.getId()); | ||
writeInstant(out, session.getCreationTime()); | ||
writeInstant(out, session.getLastAccessedTime()); | ||
writeDuration(out, session.getMaxInactiveInterval()); | ||
for (String attrName : session.getAttributeNames()) { | ||
Object attrValue = session.getAttribute(attrName); | ||
if (attrValue != null) { | ||
out.writeUTF(attrName); | ||
out.writeObject(attrValue); | ||
} | ||
} | ||
} | ||
|
||
private void writeInstant(ObjectDataOutput out, Instant instant) throws IOException { | ||
out.writeLong(instant.getEpochSecond()); | ||
out.writeInt(instant.getNano()); | ||
} | ||
|
||
private void writeDuration(ObjectDataOutput out, Duration duration) throws IOException { | ||
out.writeLong(duration.getSeconds()); | ||
out.writeInt(duration.getNano()); | ||
} | ||
|
||
@Override | ||
public MapSession read(ObjectDataInput in) throws IOException { | ||
String originalId = in.readUTF(); | ||
MapSession cached = new MapSession(originalId); | ||
cached.setId(in.readUTF()); | ||
cached.setCreationTime(readInstant(in)); | ||
cached.setLastAccessedTime(readInstant(in)); | ||
cached.setMaxInactiveInterval(readDuration(in)); | ||
try { | ||
while (true) { | ||
// During write, it's not possible to write | ||
// number of non-null attributes without an extra | ||
// iteration. Hence the attributes are read until | ||
// EOF here. | ||
String attrName = in.readUTF(); | ||
Object attrValue = in.readObject(); | ||
cached.setAttribute(attrName, attrValue); | ||
} | ||
} | ||
catch (EOFException ignored) { | ||
} | ||
return cached; | ||
} | ||
|
||
private Instant readInstant(ObjectDataInput in) throws IOException { | ||
long seconds = in.readLong(); | ||
int nanos = in.readInt(); | ||
return Instant.ofEpochSecond(seconds, nanos); | ||
} | ||
|
||
private Duration readDuration(ObjectDataInput in) throws IOException { | ||
long seconds = in.readLong(); | ||
int nanos = in.readInt(); | ||
return Duration.ofSeconds(seconds, nanos); | ||
} | ||
|
||
@Override | ||
public int getTypeId() { | ||
return SERIALIZER_TYPE_ID; | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
} | ||
|
||
} |
99 changes: 99 additions & 0 deletions
99
.../src/test/java/org/springframework/session/hazelcast/HazelcastSessionSerializerTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* Copyright 2014-2020 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.session.hazelcast; | ||
|
||
import java.io.Serializable; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
|
||
import com.hazelcast.config.SerializationConfig; | ||
import com.hazelcast.config.SerializerConfig; | ||
import com.hazelcast.internal.serialization.impl.DefaultSerializationServiceBuilder; | ||
import com.hazelcast.nio.serialization.Data; | ||
import com.hazelcast.spi.serialization.SerializationService; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import org.springframework.session.MapSession; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
class HazelcastSessionSerializerTests { | ||
|
||
private SerializationService serializationService; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
SerializationConfig serializationConfig = new SerializationConfig(); | ||
SerializerConfig serializerConfig = new SerializerConfig().setImplementation(new HazelcastSessionSerializer()) | ||
.setTypeClass(MapSession.class); | ||
serializationConfig.addSerializerConfig(serializerConfig); | ||
this.serializationService = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); | ||
} | ||
|
||
@Test | ||
void serializeSessionWithStreamSerializer() { | ||
MapSession originalSession = new MapSession(); | ||
originalSession.setAttribute("attr1", "value1"); | ||
originalSession.setAttribute("attr2", "value2"); | ||
originalSession.setAttribute("attr3", new SerializableTestAttribute(3)); | ||
originalSession.setMaxInactiveInterval(Duration.ofDays(5)); | ||
originalSession.setLastAccessedTime(Instant.now()); | ||
originalSession.setId("custom-id"); | ||
|
||
Data serialized = this.serializationService.toData(originalSession); | ||
MapSession cached = this.serializationService.toObject(serialized); | ||
|
||
assertThat(originalSession.getCreationTime()).isEqualTo(cached.getCreationTime()); | ||
assertThat(originalSession.getMaxInactiveInterval()).isEqualTo(cached.getMaxInactiveInterval()); | ||
assertThat(originalSession.getId()).isEqualTo(cached.getId()); | ||
assertThat(originalSession.getOriginalId()).isEqualTo(cached.getOriginalId()); | ||
assertThat(originalSession.getAttributeNames().size()).isEqualTo(cached.getAttributeNames().size()); | ||
assertThat(originalSession.<String>getAttribute("attr1")).isEqualTo(cached.getAttribute("attr1")); | ||
assertThat(originalSession.<String>getAttribute("attr2")).isEqualTo(cached.getAttribute("attr2")); | ||
assertThat(originalSession.<SerializableTestAttribute>getAttribute("attr3")) | ||
.isEqualTo(cached.getAttribute("attr3")); | ||
} | ||
|
||
static class SerializableTestAttribute implements Serializable { | ||
|
||
private int id; | ||
|
||
SerializableTestAttribute(int id) { | ||
this.id = id; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (!(o instanceof SerializableTestAttribute)) { | ||
return false; | ||
} | ||
SerializableTestAttribute that = (SerializableTestAttribute) o; | ||
return this.id == that.id; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return this.id; | ||
} | ||
|
||
} | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.