Last active
December 20, 2019 15:25
-
-
Save sgrimm/aed2d7707826f42869969154dc96745b to your computer and use it in GitHub Desktop.
Compact-format XStream converters for Axon 4 tracking tokens
This file contains 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
package com.thesegovia.payment.serial | |
import com.thoughtworks.xstream.converters.Converter | |
import com.thoughtworks.xstream.converters.MarshallingContext | |
import com.thoughtworks.xstream.converters.UnmarshallingContext | |
import com.thoughtworks.xstream.io.HierarchicalStreamReader | |
import com.thoughtworks.xstream.io.HierarchicalStreamWriter | |
import org.axonframework.eventhandling.GapAwareTrackingToken | |
/** | |
* Converts GapAwareTrackingToken to and from XML. The representation is designed to be compact | |
* while still remaining more or less human-readable: `<gatt i="123456" g="123450,123451,123452"/>`. | |
* | |
* To support migrating a clustered application to the new format, this can run in a bidirectionally | |
* compatible mode where it writes both the old and new formats. Upgrade the whole cluster to this | |
* converter, then turn off backward compatibility (by not passing a fallback converter) to get the | |
* compact form. | |
* | |
* TODO: Detect contiguous ranges of gaps and write them in a more compact form. | |
*/ | |
class GapAwareTrackingTokenConverter( | |
/** Converter to use to write tokens in backward-compatible form. */ | |
private val backwardCompatibleConverter: Converter? = null | |
) : Converter { | |
override fun unmarshal(reader: HierarchicalStreamReader, context: UnmarshallingContext): Any { | |
val index = reader.getAttribute("i") | |
if (index != null) { | |
val gaps = reader.getAttribute("g")?.split(',')?.map { it.toLong() } | |
return GapAwareTrackingToken.newInstance( | |
index.toLong(), | |
gaps ?: emptyList() | |
) | |
} | |
// Unmarshal old-style format | |
return backwardCompatibleConverter?.unmarshal(reader, context) | |
?: throw RuntimeException("No index attribute found") | |
} | |
override fun marshal( | |
original: Any, | |
writer: HierarchicalStreamWriter, | |
context: MarshallingContext | |
) { | |
if (original !is GapAwareTrackingToken) { | |
throw RuntimeException("Unable to marshal object of type ${original.javaClass.name}") | |
} | |
writer.addAttribute("i", original.index.toString()) | |
if (original.gaps.isNotEmpty()) { | |
writer.addAttribute("g", original.gaps.joinToString(",") { "$it" }) | |
} | |
backwardCompatibleConverter?.marshal(original, writer, context) | |
} | |
override fun canConvert(type: Class<*>): Boolean { | |
return type.name == GapAwareTrackingToken::class.java.name | |
} | |
} |
This file contains 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
package com.thesegovia.payment.serial | |
import com.thoughtworks.xstream.converters.Converter | |
import com.thoughtworks.xstream.converters.MarshallingContext | |
import com.thoughtworks.xstream.converters.UnmarshallingContext | |
import com.thoughtworks.xstream.io.HierarchicalStreamReader | |
import com.thoughtworks.xstream.io.HierarchicalStreamWriter | |
import org.axonframework.eventhandling.GapAwareTrackingToken | |
import org.axonframework.eventhandling.MultiSourceTrackingToken | |
import org.axonframework.eventhandling.TrackingToken | |
/** | |
* Converts MultiSourceTrackingToken to and from XML. The representation is designed to be compact | |
* but still human-readable: `<mstt><t n="name1" i="12345"/><t n="name2" i="319479"/></mstt>`. | |
* | |
* To support migrating a clustered application to the new format, this can run in a bidirectionally | |
* compatible mode where it writes both the old and new formats. In that case it includes an | |
* additional attribute in the top-level tag so it can detect a new-style token. Upgrade the whole | |
* cluster to this converter, then turn off backward compatibility (by not passing a fallback | |
* converter) to get the compact form. | |
* | |
* This prototype implementation assumes that the underlying tokens are all [GapAwareTrackingToken]. | |
* | |
* TODO: Cleaner backward compatibility logic; we could look at the child tags rather than adding | |
* a new attribute. | |
* | |
* TODO: Support underlying tokens of arbitrary type. | |
* | |
* TODO: Support not including names at all (if the caller passes in a list of names, we can | |
* make the underlying tokens represent those names in that order). | |
* | |
* TODO: If the tokens are all GapAwareTrackingToken and we have a fixed list of names, we could | |
* ultimately render this as something like `<mstt i="12345,319479" g="12344;319475,319477"/>` | |
* when there are gaps, or `<mstt i="12345,319479"/>` when there aren't. | |
*/ | |
class MultiSourceTrackingTokenConverter( | |
/** Converter to use to write tokens in backward-compatible form. */ | |
private val backwardCompatibleConverter: Converter? = null | |
) : Converter { | |
override fun unmarshal(reader: HierarchicalStreamReader, context: UnmarshallingContext): Any { | |
if (reader.attributeCount > 0 || backwardCompatibleConverter == null) { | |
// We marshalled this object. | |
val tokens = mutableMapOf<String, TrackingToken>() | |
while (reader.hasMoreChildren()) { | |
try { | |
reader.moveDown() | |
val name = reader.getAttribute("n") | |
val token = context.convertAnother(null, GapAwareTrackingToken::class.java) | |
tokens[name] = token as TrackingToken | |
} finally { | |
reader.moveUp() | |
} | |
} | |
return MultiSourceTrackingToken(tokens) | |
} | |
// Unmarshal old-style format | |
return backwardCompatibleConverter.unmarshal(reader, context) | |
} | |
override fun marshal( | |
original: Any, | |
writer: HierarchicalStreamWriter, | |
context: MarshallingContext | |
) { | |
if (original !is MultiSourceTrackingToken) { | |
throw RuntimeException("Unable to marshal object of type ${original.javaClass.name}") | |
} | |
if (backwardCompatibleConverter != null) { | |
writer.addAttribute("v", "1") | |
} | |
original.trackingTokens.forEach { (name, token) -> | |
writer.startNode("t") | |
context.convertAnother(token) | |
writer.addAttribute("n", name) | |
writer.endNode() | |
} | |
backwardCompatibleConverter?.marshal(original, writer, context) | |
} | |
override fun canConvert(type: Class<*>): Boolean { | |
return type.name == MultiSourceTrackingToken::class.java.name | |
} | |
} |
This file contains 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
private static void configureCompactTrackingTokens( | |
XStreamSerializer serializer, boolean backwardCompatibilityMode) { | |
XStream xStream = serializer.getXStream(); | |
serializer.addAlias("mstt", MultiSourceTrackingToken.class); | |
serializer.addAlias("gatt", GapAwareTrackingToken.class); | |
xStream.registerConverter( | |
new GapAwareTrackingTokenConverter( | |
backwardCompatibilityMode | |
? xStream.getConverterLookup().lookupConverterForType(GapAwareTrackingToken.class) | |
: null)); | |
xStream.registerConverter( | |
new MultiSourceTrackingTokenConverter( | |
backwardCompatibilityMode | |
? xStream | |
.getConverterLookup() | |
.lookupConverterForType(MultiSourceTrackingToken.class) | |
: null)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment