diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineContext.java index ccd4e863..68d49498 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineContext.java @@ -126,7 +126,7 @@ public class DefaultStateMachineContext implements StateMachineContext> childs, S state, E event, Map eventHeaders, ExtendedState extendedState, Map historyStates, String id) { this.childs = childs; - this.childRefs = null; + this.childRefs = new ArrayList<>(); this.state = state; this.event = event; this.eventHeaders = eventHeaders; diff --git a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java index becf0d63..7fa6d00f 100644 --- a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java +++ b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.kryo; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -37,6 +38,9 @@ import com.esotericsoftware.kryo.io.Output; */ public class StateMachineContextSerializer extends Serializer> { + // NOTE: when structure of this serialisation is changed, see how things are tested + // in StateMachineContextSerializerTests. + @Override public void write(Kryo kryo, Output output, StateMachineContext context) { kryo.writeClassAndObject(output, context.getEvent()); @@ -46,9 +50,10 @@ public class StateMachineContextSerializer extends Serializer extends Serializer> childs = (List>) kryo.readClassAndObject(input); Map historyStates = (Map) kryo.readClassAndObject(input); String id = (String) kryo.readClassAndObject(input); - List childRefs = (List) kryo.readClassAndObject(input); + List childRefs = new ArrayList<>(); + if(input.canReadInt()) { + childRefs = (List) kryo.readClassAndObject(input); + } + return new DefaultStateMachineContext(childRefs, childs, state, event, eventHeaders, new DefaultExtendedState(variables), historyStates, id); } diff --git a/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/StateMachineContextSerializerTests.java b/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/StateMachineContextSerializerTests.java index e08b32b9..c8c68988 100644 --- a/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/StateMachineContextSerializerTests.java +++ b/spring-statemachine-kryo/src/test/java/org/springframework/statemachine/kryo/StateMachineContextSerializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-2019 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. @@ -15,41 +15,41 @@ */ package org.springframework.statemachine.kryo; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.junit.Before; import org.junit.Test; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.support.DefaultExtendedState; import org.springframework.statemachine.support.DefaultStateMachineContext; import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; /** * Tests for {@link StateMachineContextSerializer}. + *

+ * When StateMachineContextSerializer structure is changes, copy previous + * version here and test raw bytes from old serializer against new one and other + * combinations as needed. * * @author Janne Valkealahti * */ public class StateMachineContextSerializerTests { - private Kryo kryo; - private Output output; - private Input input; - - @Before - public void setUp() throws Exception { - kryo = new Kryo(); - } - @Test public void testContextWithChilds() { + Kryo kryo = new Kryo(); StateMachineContextSerializer serializer = new StateMachineContextSerializer<>(); kryo.addDefaultSerializer(StateMachineContext.class, serializer); @@ -61,11 +61,75 @@ public class StateMachineContextSerializerTests { new HashMap(), new DefaultExtendedState()); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - output = new Output(outStream); + Output output = new Output(outStream); kryo.writeClassAndObject(output, root); output.flush(); - input = new Input(new ByteArrayInputStream(outStream.toByteArray())); + Input input = new Input(new ByteArrayInputStream(outStream.toByteArray())); kryo.readClassAndObject(input); } + + @SuppressWarnings("unchecked") + @Test + public void testContextFromInitialVersionToCurrent() { + // test added for PR #722 + // as a time writing this test, we had two version, initial(V1) + // and current(V2). raw bytes from V1 to V2. + Kryo kryoFrom = new Kryo(); + Kryo kryoTo = new Kryo(); + + StateMachineContextSerializerV1 serializerV1 = new StateMachineContextSerializerV1<>(); + kryoFrom.addDefaultSerializer(StateMachineContext.class, serializerV1); + + StateMachineContext childFrom = new DefaultStateMachineContext("child", "event1", + new HashMap(), new DefaultExtendedState()); + List> childsFrom = new ArrayList<>(); + childsFrom.add(childFrom); + StateMachineContext rootFrom = new DefaultStateMachineContext("root", "event2", + new HashMap(), new DefaultExtendedState()); + + ByteArrayOutputStream outStreamFrom = new ByteArrayOutputStream(); + Output outputFrom = new Output(outStreamFrom); + kryoFrom.writeClassAndObject(outputFrom, rootFrom); + outputFrom.flush(); + + StateMachineContextSerializer serializerCurrent = new StateMachineContextSerializer<>(); + kryoTo.addDefaultSerializer(StateMachineContext.class, serializerCurrent); + + Input inputTo = new Input(new ByteArrayInputStream(outStreamFrom.toByteArray())); + StateMachineContext rootTo = (StateMachineContext) kryoTo.readClassAndObject(inputTo); + assertThat(rootFrom, equalTo(rootTo)); + } + + /** + * Initial implementation of a StateMachineContextSerializer which is used to + * test read to current version. + */ + private static class StateMachineContextSerializerV1 extends Serializer> { + + @Override + public void write(Kryo kryo, Output output, StateMachineContext context) { + kryo.writeClassAndObject(output, context.getEvent()); + kryo.writeClassAndObject(output, context.getState()); + kryo.writeClassAndObject(output, context.getEventHeaders()); + kryo.writeClassAndObject(output, context.getExtendedState() != null ? context.getExtendedState().getVariables() : null); + kryo.writeClassAndObject(output, context.getChilds()); + kryo.writeClassAndObject(output, context.getHistoryStates()); + kryo.writeClassAndObject(output, context.getId()); + } + + @SuppressWarnings("unchecked") + @Override + public StateMachineContext read(Kryo kryo, Input input, Class> clazz) { + E event = (E) kryo.readClassAndObject(input); + S state = (S) kryo.readClassAndObject(input); + Map eventHeaders = (Map) kryo.readClassAndObject(input); + Map variables = (Map) kryo.readClassAndObject(input); + List> childs = (List>) kryo.readClassAndObject(input); + Map historyStates = (Map) kryo.readClassAndObject(input); + String id = (String) kryo.readClassAndObject(input); + return new DefaultStateMachineContext(childs, state, event, eventHeaders, + new DefaultExtendedState(variables), historyStates, id); + } + } }