Skip to content

Commit

Permalink
#806 Support custom ClassLookup in WireTypeConverter
Browse files Browse the repository at this point in the history
  • Loading branch information
maxim-ponomarev committed Jan 10, 2024
1 parent 2ca5caf commit 4756379
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 50 deletions.
18 changes: 14 additions & 4 deletions src/main/java/net/openhft/chronicle/wire/WireTypeConverter.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
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);
}

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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Object, Object> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 className = name.toString();
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,104 +1,129 @@
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;
import net.openhft.chronicle.wire.Wire;
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<Exception> onException;

private ExceptionCatchingClassLookup(ClassLookup delegate, Consumer<Exception> 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);
}
}

/**
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.openhft.chronicle.wire;

import net.openhft.chronicle.core.pool.ClassAliasPool;
import net.openhft.chronicle.wire.internal.UnknownClassLookup;
import org.junit.Test;

public class WireTypeConverterWithUnknownClassesLookupTest extends WireTestCommon {

@Test
public void fromYamlToJsonAndBackToYaml() {
WireTypeConverter wireTypeConverter = new WireTypeConverter(new UnknownClassLookup(ClassAliasPool.CLASS_ALIASES));
String yaml = "!ChronicleServicesCfg {\n" +
" queues: {\n" +
" in: { path: tmp/benchmark/in },\n" +
" sender-one-out: { path: tmp/benchmark/sender-one-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" sender-two-out: { path: tmp/benchmark/sender-two-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" sender-three-out: { path: tmp/benchmark/sender-three-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" receiver-out: { path: tmp/benchmark/receiver-out },\n" +
" },\n" +
" services: {\n" +
" sender-one: {\n" +
" inputs: [ in ],\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" +
"}";
CharSequence json = wireTypeConverter.yamlToJson(yaml);
System.out.println(json);

CharSequence backToYaml = wireTypeConverter.jsonToYaml(json);
System.out.println(backToYaml);
}

@Test
public void typeReference() {
WireTypeConverter wireTypeConverter = new WireTypeConverter(new UnknownClassLookup(ClassAliasPool.CLASS_ALIASES));
String yaml = "!ChronicleServicesCfg {\n" +
" queues: {\n" +
" in: { path: tmp/benchmark/in },\n" +
" sender-one-out: { path: tmp/benchmark/sender-one-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" sender-two-out: { path: tmp/benchmark/sender-two-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" sender-three-out: { path: tmp/benchmark/sender-three-out, builder: !SingleChronicleQueueBuilder { useSparseFiles: true, rollCycle: HUGE_DAILY } },\n" +
" receiver-out: { path: tmp/benchmark/receiver-out },\n" +
" },\n" +
" services: {\n" +
" sender-one: {\n" +
" inputs: [ in ],\n" +
" output: sender-one-out,\n" +
" startFromStrategy: START,\n" +
" affinityCpu: any,\n" +
" pretouchMS: 100,\n" +
" implClass: !type non.existing.package.SenderOneService,\n" +
" serviceConfig: {\n" +
" param: !CustomClass1 {\n" +
" param2: value\n" +
" }\n" +
" }\n" +
" }\n" +
"}";

CharSequence json = wireTypeConverter.yamlToJson(yaml);
System.out.println(json);

CharSequence backToYaml = wireTypeConverter.jsonToYaml(json);
System.out.println(backToYaml);
}
}

0 comments on commit 4756379

Please sign in to comment.