diff --git a/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java b/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java index 1954480bda..f23381081c 100644 --- a/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java +++ b/src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java @@ -1,11 +1,21 @@ package net.openhft.chronicle.wire; +import net.openhft.chronicle.core.pool.ClassLookup; import net.openhft.chronicle.wire.internal.WireTypeConverterInternal; +import org.jetbrains.annotations.NotNull; public class WireTypeConverter { private final WireTypeConverterInternal delegate; - public WireTypeConverter(Validate validate) { + public WireTypeConverter(@NotNull Validate validate, @NotNull ClassLookup classLookup) { + delegate = new WireTypeConverterInternal(validate, classLookup); + } + + public WireTypeConverter(@NotNull ClassLookup classLookup) { + delegate = new WireTypeConverterInternal(classLookup); + } + + public WireTypeConverter(@NotNull Validate validate) { delegate = new WireTypeConverterInternal(validate); } @@ -13,15 +23,15 @@ public WireTypeConverter() { delegate = new WireTypeConverterInternal(); } - public CharSequence jsonToYaml(CharSequence json) throws Exception { + public CharSequence jsonToYaml(CharSequence json) { return delegate.jsonToYaml(json); } - public CharSequence yamlToJson(CharSequence yaml) throws Exception { + public CharSequence yamlToJson(CharSequence yaml) { return delegate.yamlToJson(yaml); } - public void addAlias(Class newClass, String oldTypeName) { + public void addAlias(Class newClass, String oldTypeName) { delegate.addAlias(newClass, oldTypeName); } } diff --git a/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassBase.java b/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassBase.java new file mode 100644 index 0000000000..f9e9e5a00e --- /dev/null +++ b/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassBase.java @@ -0,0 +1,25 @@ +package net.openhft.chronicle.wire.internal; + +import net.openhft.chronicle.core.io.IORuntimeException; +import net.openhft.chronicle.core.io.InvalidMarshallableException; +import net.openhft.chronicle.wire.SelfDescribingMarshallable; +import net.openhft.chronicle.wire.WireIn; +import net.openhft.chronicle.wire.WireOut; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class UnknownClassBase extends SelfDescribingMarshallable { + private Map map = new LinkedHashMap<>(); + + @Override + public void readMarshallable(@NotNull WireIn wire) throws IORuntimeException, InvalidMarshallableException { + map = wire.readAllAsMap(Object.class, Object.class, new LinkedHashMap<>()); + } + + @Override + public void writeMarshallable(@NotNull WireOut wire) throws InvalidMarshallableException { + wire.writeAllAsMap(Object.class, Object.class, map); + } +} \ No newline at end of file diff --git a/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassLookup.java b/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassLookup.java new file mode 100644 index 0000000000..bf228517bb --- /dev/null +++ b/src/main/java/net/openhft/chronicle/wire/internal/UnknownClassLookup.java @@ -0,0 +1,55 @@ +package net.openhft.chronicle.wire.internal; + +import net.openhft.chronicle.core.Jvm; +import net.openhft.chronicle.core.OS; +import net.openhft.chronicle.core.pool.ClassLookup; +import net.openhft.chronicle.core.util.ClassNotFoundRuntimeException; +import net.openhft.compiler.CachedCompiler; + +import java.io.File; + +public class UnknownClassLookup implements ClassLookup { + public static final CachedCompiler CACHED_COMPILER = + new CachedCompiler(Jvm.isDebug() ? new File(OS.getTarget(), "generated-test-sources") : null, null); + + private final ClassLookup delegate; + + public UnknownClassLookup(ClassLookup delegate) { + this.delegate = delegate; + } + + @Override + public Class forName(CharSequence name) throws ClassNotFoundRuntimeException { + try { + return delegate.forName(name); + } catch (Exception e) { + String[] parts = name.toString().split("\\."); + String className = parts[parts.length - 1]; + Class unknownClass; + try { + unknownClass = CACHED_COMPILER.loadFromJava(className, + "public class " + className + " extends " + UnknownClassBase.class.getName() + "{}"); + } catch (ClassNotFoundException ex) { + throw new ClassNotFoundRuntimeException(ex); + } + + addAlias(unknownClass, className); + return delegate.forName(name); + } + } + + @Override + public String nameFor(Class clazz) throws IllegalArgumentException { + return delegate.nameFor(clazz); + } + + @Override + public void addAlias(Class... classes) { + delegate.addAlias(classes); + } + + @Override + public void addAlias(Class clazz, String names) { + delegate.addAlias(clazz, names); + } +} diff --git a/src/main/java/net/openhft/chronicle/wire/internal/WireTypeConverterInternal.java b/src/main/java/net/openhft/chronicle/wire/internal/WireTypeConverterInternal.java index de2cb19f30..910555b6e2 100644 --- a/src/main/java/net/openhft/chronicle/wire/internal/WireTypeConverterInternal.java +++ b/src/main/java/net/openhft/chronicle/wire/internal/WireTypeConverterInternal.java @@ -1,6 +1,8 @@ package net.openhft.chronicle.wire.internal; import net.openhft.chronicle.bytes.Bytes; +import net.openhft.chronicle.core.Jvm; +import net.openhft.chronicle.core.pool.ClassAliasPool; import net.openhft.chronicle.core.pool.ClassLookup; import net.openhft.chronicle.core.util.ClassNotFoundRuntimeException; import net.openhft.chronicle.wire.Validate; @@ -8,97 +10,120 @@ import net.openhft.chronicle.wire.WireType; import org.jetbrains.annotations.NotNull; +import java.util.function.Consumer; + public class WireTypeConverterInternal { private static final Validate NO_OP = x -> { }; - private final Bytes bytes = Bytes.allocateElasticOnHeap(); + private final Bytes bytes = Bytes.allocateElasticOnHeap(); private final Wire yamlWire = WireType.YAML_ONLY.apply(bytes); private final Wire jsonWire = WireType.JSON_ONLY.apply(bytes); private final Validate validate; + + private final ExceptionCatchingClassLookup exceptionCatchingClassLookup; + private Exception e; - public WireTypeConverterInternal(@NotNull Validate validate) { + public WireTypeConverterInternal(@NotNull Validate validate, @NotNull ClassLookup classLookup) { this.validate = validate; - replaceClassLookup(jsonWire); - replaceClassLookup(yamlWire); + this.exceptionCatchingClassLookup = + new ExceptionCatchingClassLookup(classLookup, this::onException); + jsonWire.classLookup(exceptionCatchingClassLookup); + yamlWire.classLookup(exceptionCatchingClassLookup); + } + + public WireTypeConverterInternal(@NotNull Validate validate) { + this(validate, ClassAliasPool.CLASS_ALIASES); + } + + public WireTypeConverterInternal(@NotNull ClassLookup classLookup) { + this(NO_OP, classLookup); } public WireTypeConverterInternal() { - this.validate = NO_OP; - replaceClassLookup(jsonWire); - replaceClassLookup(yamlWire); + this(NO_OP, ClassAliasPool.CLASS_ALIASES); + } + + private void onException(Exception e) { + this.e = e; } - public CharSequence jsonToYaml(CharSequence json) throws Exception { + public CharSequence jsonToYaml(CharSequence json) { e = null; jsonWire.reset(); jsonWire.bytes().append(json); Object object = jsonWire.getValueIn().object(); if (e != null) - throw e; + throw Jvm.rethrow(e); + validate.validate(object); yamlWire.reset(); yamlWire.getValueOut().object(object); if (e != null) - throw e; + throw Jvm.rethrow(e); return yamlWire.bytes(); } - public CharSequence yamlToJson(CharSequence yaml) throws Exception { + public CharSequence yamlToJson(CharSequence yaml) { e = null; yamlWire.reset(); yamlWire.bytes().clear().append(yaml); Object object = yamlWire.getValueIn().object(); if (e != null) - throw e; + throw Jvm.rethrow(e); validate.validate(object); jsonWire.reset(); jsonWire.bytes().clear(); jsonWire.getValueOut().object(object); if (e != null) - throw e; + throw Jvm.rethrow(e); + return jsonWire.bytes(); } - private void replaceClassLookup(Wire wire) { - final ClassLookup delegate = wire.classLookup(); - wire.classLookup(new ClassLookup() { - - @Override - public Class forName(CharSequence name) throws ClassNotFoundRuntimeException { - try { - return delegate.forName(name); - } catch (Exception e) { - WireTypeConverterInternal.this.e = e; - throw e; - } + private static class ExceptionCatchingClassLookup implements ClassLookup { + private final ClassLookup delegate; + private final Consumer onException; + + private ExceptionCatchingClassLookup(ClassLookup delegate, Consumer onException) { + this.delegate = delegate; + this.onException = onException; + } + + @Override + public Class forName(CharSequence name) throws ClassNotFoundRuntimeException { + try { + return delegate.forName(name); + } catch (Exception e) { + onException.accept(e); + throw e; } - - @Override - public String nameFor(Class clazz) throws IllegalArgumentException { - try { - return delegate.nameFor(clazz); - } catch (Exception e) { - WireTypeConverterInternal.this.e = e; - throw e; - } + } + + @Override + public String nameFor(Class clazz) throws IllegalArgumentException { + try { + return delegate.nameFor(clazz); + } catch (Exception e) { + onException.accept(e); + throw e; } + } - @Override - public void addAlias(Class... classes) { - delegate.addAlias(classes); - } + @Override + public void addAlias(Class... classes) { + delegate.addAlias(classes); + } - @Override - public void addAlias(Class clazz, String names) { - delegate.addAlias(clazz, names); - } - }); + @Override + public void addAlias(Class clazz, String names) { + delegate.addAlias(clazz, names); + } } /** @@ -107,8 +132,7 @@ public void addAlias(Class clazz, String names) { * @param newClass to use instead * @param oldTypeName to support */ - public void addAlias(Class newClass, String oldTypeName) { - jsonWire.classLookup().addAlias(newClass, oldTypeName); - yamlWire.classLookup().addAlias(newClass, oldTypeName); + public void addAlias(Class newClass, String oldTypeName) { + exceptionCatchingClassLookup.addAlias(newClass, oldTypeName); } } diff --git a/src/test/java/net/openhft/chronicle/wire/WireTypeConverterWithUnknownClassesLookupTest.java b/src/test/java/net/openhft/chronicle/wire/WireTypeConverterWithUnknownClassesLookupTest.java new file mode 100644 index 0000000000..5a7fe63b6c --- /dev/null +++ b/src/test/java/net/openhft/chronicle/wire/WireTypeConverterWithUnknownClassesLookupTest.java @@ -0,0 +1,129 @@ +package net.openhft.chronicle.wire; + +import net.openhft.chronicle.core.pool.ClassAliasPool; +import net.openhft.chronicle.wire.internal.UnknownClassLookup; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WireTypeConverterWithUnknownClassesLookupTest { //extends WireTestCommon { + @Test + public void fromYamlToJsonAndBackToYaml() { + WireTypeConverter wireTypeConverter = new WireTypeConverter(new UnknownClassLookup(ClassAliasPool.CLASS_ALIASES)); + Wires.GENERATE_TUPLES = true; + Wires.THROW_CNFRE = false; + String yaml = "!ChronicleServicesCfg {\n" + + " queues: {\n" + + " in: {\n" + + " path: tmp/benchmark/in\n" + + " },\n" + + " sender-one-out: {\n" + + " path: tmp/benchmark/sender-one-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " sender-two-out: {\n" + + " path: tmp/benchmark/sender-two-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " sender-three-out: {\n" + + " path: tmp/benchmark/sender-three-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " receiver-out: {\n" + + " path: tmp/benchmark/receiver-out\n" + + " }\n" + + " },\n" + + " services: {\n" + + " sender-one: {\n" + + " inputs: [\n" + + " in\n" + + " ],\n" + + " output: sender-one-out,\n" + + " startFromStrategy: $property.name,\n" + + " affinityCpu: any,\n" + + " pretouchMS: 100,\n" + + " serviceConfig: {\n" + + " param: !CustomClass1 {\n" + + " param2: value\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n"; + + CharSequence json = wireTypeConverter.yamlToJson(yaml); + + CharSequence backToYaml = wireTypeConverter.jsonToYaml(json.toString()); + + assertEquals(yaml, backToYaml.toString()); + } + + @Test + public void typeReference() { + WireTypeConverter wireTypeConverter = new WireTypeConverter(new UnknownClassLookup(ClassAliasPool.CLASS_ALIASES)); + Wires.GENERATE_TUPLES = true; + Wires.THROW_CNFRE = false; + String yaml = "!ChronicleServicesCfg {\n" + + " queues: {\n" + + " in: {\n" + + " path: tmp/benchmark/in\n" + + " },\n" + + " sender-one-out: {\n" + + " path: tmp/benchmark/sender-one-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " sender-two-out: {\n" + + " path: tmp/benchmark/sender-two-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " sender-three-out: {\n" + + " path: tmp/benchmark/sender-three-out,\n" + + " builder: !SingleChronicleQueueBuilder {\n" + + " useSparseFiles: true,\n" + + " rollCycle: HUGE_DAILY\n" + + " }\n" + + " },\n" + + " receiver-out: {\n" + + " path: tmp/benchmark/receiver-out\n" + + " }\n" + + " },\n" + + " services: {\n" + + " sender-one: {\n" + + " inputs: [\n" + + " in\n" + + " ],\n" + + " output: sender-one-out,\n" + + " startFromStrategy: START,\n" + + " affinityCpu: any,\n" + + " pretouchMS: 100,\n" + + " serviceConfig: {\n" + + " param: !CustomClass1 {\n" + + " param2: value\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n"; + + CharSequence json = wireTypeConverter.yamlToJson(yaml); + + CharSequence backToYaml = wireTypeConverter.jsonToYaml(json.toString()); + + assertEquals(yaml, backToYaml.toString()); + } +}