Skip to content

Commit

Permalink
Eliminate bson dependency on mongo
Browse files Browse the repository at this point in the history
  • Loading branch information
prdoyle committed Jan 19, 2025
1 parent b0b17f5 commit dfbf8e2
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 87 deletions.
1 change: 0 additions & 1 deletion bosk-bson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ dependencies {

testImplementation project(":bosk-logback")
testImplementation project(":bosk-testing")
testImplementation project(":bosk-mongo") // No surprise that MongoDB is handy for testing BSON
testImplementation project(":lib-testing")
}

Expand Down
84 changes: 81 additions & 3 deletions bosk-bson/src/main/java/works/bosk/bson/BsonFormatter.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,61 @@
package works.bosk.bson;

import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWriter;
import org.bson.BsonReader;
import org.bson.BsonValue;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.DocumentCodecProvider;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.ValueCodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import works.bosk.BoskInfo;
import works.bosk.Listing;
import works.bosk.Reference;
import works.bosk.SerializationPlugin;
import works.bosk.SideTable;

public class BsonSurgeonFormatter {
import static works.bosk.ReferenceUtils.rawClass;

public class BsonFormatter {
protected static final UnaryOperator<String> DECODER;
protected static final UnaryOperator<String> ENCODER;
protected final CodecRegistry simpleCodecs;
protected final Function<Type, Codec<?>> preferredBoskCodecs;
protected final Function<Reference<?>, SerializationPlugin.DeserializationScope> deserializationScopeFunction;

public BsonFormatter(BoskInfo<?> boskInfo, BsonPlugin bsonPlugin) {
this.simpleCodecs = CodecRegistries.fromProviders(
bsonPlugin.codecProviderFor(boskInfo),
new ValueCodecProvider(),
new DocumentCodecProvider());
this.preferredBoskCodecs = type -> bsonPlugin.getCodec(type, rawClass(type), simpleCodecs, boskInfo);
this.deserializationScopeFunction = bsonPlugin::newDeserializationScope;
}

/**
* @param refLength behave as though <code>ref</code> were truncated to this length without actually having to do it
*/
private static <T> void buildDottedFieldNameOf(Reference<T> ref, int startLength, int refLength, ArrayList<String> segments) {
if (ref.path().length() > startLength) {
Reference<?> enclosingReference = ref.enclosingReference(Object.class);
BsonSurgeonFormatter.buildDottedFieldNameOf(enclosingReference, startLength, refLength, segments);
BsonFormatter.buildDottedFieldNameOf(enclosingReference, startLength, refLength, segments);
if (ref.path().length() <= refLength) {
if (Listing.class.isAssignableFrom(enclosingReference.targetClass())) {
segments.add("ids");
} else if (SideTable.class.isAssignableFrom(enclosingReference.targetClass())) {
segments.add("valuesById");
}
segments.add(BsonSurgeonFormatter.dottedFieldNameSegment(ref.path().lastSegment()));
segments.add(BsonFormatter.dottedFieldNameSegment(ref.path().lastSegment()));
}
}
}
Expand Down Expand Up @@ -95,6 +124,55 @@ private static char hexCharForDigit(int value) {
}
}

protected Codec<?> codecFor(Type type) {
// BsonPlugin gives better codecs than CodecRegistry, because BsonPlugin is aware of generics,
// so we always try that first. The CodecSupplier protocol uses "null" to indicate that another
// CodecSupplier should be used, so we follow that protocol and fall back on the CodecRegistry.
// TODO: Should this logic be in BsonPlugin? It has nothing to do with MongoDriver really.
Codec<?> result = preferredBoskCodecs.apply(type);
if (result == null) {
return simpleCodecs.get(rawClass(type));
} else {
return result;
}
}

/**
* @see #bsonValue2object(BsonValue, Reference)
*/
@SuppressWarnings("unchecked")
public <T> BsonValue object2bsonValue(T object, Type type) {
rawClass(type).cast(object);
Codec<T> objectCodec = (Codec<T>) codecFor(type);
BsonDocument document = new BsonDocument();
try (BsonDocumentWriter writer = new BsonDocumentWriter(document)) {
// To support arbitrary values, not just whole documents, we put the result INSIDE a document.
writer.writeStartDocument();
writer.writeName("value");
objectCodec.encode(writer, object, EncoderContext.builder().build());
writer.writeEndDocument();
}
return document.get("value");
}

/**
* @see #object2bsonValue(Object, Type)
*/
@SuppressWarnings("unchecked")
public <T> T bsonValue2object(BsonValue bson, Reference<T> target) {
Codec<T> objectCodec = (Codec<T>) codecFor(target.targetType());
BsonDocument document = new BsonDocument();
document.append("value", bson);
try (
@SuppressWarnings("unused") SerializationPlugin.DeserializationScope scope = deserializationScopeFunction.apply(target);
BsonReader reader = document.asBsonReader()
) {
reader.readStartDocument();
reader.readName("value");
return objectCodec.decode(reader, DecoderContext.builder().build());
}
}

/**
* The fields of the main MongoDB document. Case-sensitive.
*
Expand Down
4 changes: 2 additions & 2 deletions bosk-bson/src/main/java/works/bosk/bson/BsonPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
import static works.bosk.ReferenceUtils.getterMethod;
import static works.bosk.ReferenceUtils.parameterType;
import static works.bosk.ReferenceUtils.rawClass;
import static works.bosk.bson.BsonSurgeonFormatter.dottedFieldNameSegment;
import static works.bosk.bson.BsonSurgeonFormatter.undottedFieldNameSegment;
import static works.bosk.bson.BsonFormatter.dottedFieldNameSegment;
import static works.bosk.bson.BsonFormatter.undottedFieldNameSegment;

public final class BsonPlugin extends SerializationPlugin {
private final ValueCodecProvider valueCodecProvider = new ValueCodecProvider();
Expand Down
8 changes: 4 additions & 4 deletions bosk-bson/src/main/java/works/bosk/bson/BsonSurgeon.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
import works.bosk.Path;
import works.bosk.Reference;
import works.bosk.SideTable;
import works.bosk.bson.BsonSurgeonFormatter.DocumentFields;
import works.bosk.bson.BsonFormatter.DocumentFields;
import works.bosk.exceptions.InvalidTypeException;

import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import static works.bosk.bson.BsonSurgeonFormatter.containerSegments;
import static works.bosk.bson.BsonSurgeonFormatter.dottedFieldNameSegments;
import static works.bosk.bson.BsonSurgeonFormatter.undottedFieldNameSegment;
import static works.bosk.bson.BsonFormatter.containerSegments;
import static works.bosk.bson.BsonFormatter.dottedFieldNameSegments;
import static works.bosk.bson.BsonFormatter.undottedFieldNameSegment;

/**
* Splits up a single large BSON document into multiple self-describing pieces,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@

import static org.junit.jupiter.api.Assertions.*;

class BsonSurgeonFormatterTest {
class BsonFormatterTest {

@ParameterizedTest
@MethodSource("dottedNameCases")
void dottedFieldNameSegment(String plain, String dotted) {
assertEquals(dotted, BsonSurgeonFormatter.dottedFieldNameSegment(plain));
assertEquals(dotted, BsonFormatter.dottedFieldNameSegment(plain));
}

@ParameterizedTest
@MethodSource("dottedNameCases")
void undottedFieldNameSegment(String plain, String dotted) {
assertEquals(plain, BsonSurgeonFormatter.undottedFieldNameSegment(dotted));
assertEquals(plain, BsonFormatter.undottedFieldNameSegment(dotted));
}

static Stream<Arguments> dottedNameCases() {
Expand Down
5 changes: 2 additions & 3 deletions bosk-bson/src/test/java/works/bosk/bson/BsonSurgeonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import works.bosk.SideTableReference;
import works.bosk.annotations.ReferencePath;
import works.bosk.drivers.AbstractDriverTest;
import works.bosk.drivers.mongo.Formatter;
import works.bosk.drivers.state.TestEntity;
import works.bosk.exceptions.InvalidTypeException;

Expand All @@ -33,7 +32,7 @@
public class BsonSurgeonTest extends AbstractDriverTest {
BsonSurgeon surgeon;
BsonPlugin bsonPlugin;
Formatter formatter;
BsonFormatter formatter;
private List<Reference<? extends EnumerableByIdentifier<?>>> graftPoints;

Refs refs;
Expand All @@ -55,7 +54,7 @@ public interface Refs {
void setup() throws InvalidTypeException, IOException, InterruptedException {
setupBosksAndReferences(Bosk.simpleDriver());
bsonPlugin = new BsonPlugin();
formatter = new Formatter(bosk, bsonPlugin);
formatter = new BsonFormatter(bosk, bsonPlugin);

refs = bosk.buildReferences(Refs.class);

Expand Down
70 changes: 3 additions & 67 deletions bosk-mongo/src/main/java/works/bosk/drivers/mongo/Formatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,19 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import lombok.NonNull;
import org.bson.BsonBinaryWriter;
import org.bson.BsonDocument;
import org.bson.BsonDocumentReader;
import org.bson.BsonDocumentWriter;
import org.bson.BsonInt32;
import org.bson.BsonInt64;
import org.bson.BsonReader;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.codecs.BsonValueCodec;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.DocumentCodecProvider;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.ValueCodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.io.BasicOutputBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -41,7 +34,7 @@
import works.bosk.SerializationPlugin;
import works.bosk.SideTable;
import works.bosk.bson.BsonPlugin;
import works.bosk.bson.BsonSurgeonFormatter;
import works.bosk.bson.BsonFormatter;
import works.bosk.exceptions.InvalidTypeException;

import static java.util.Arrays.asList;
Expand All @@ -53,10 +46,7 @@
*
* @author pdoyle
*/
public final class Formatter extends BsonSurgeonFormatter {
private final CodecRegistry simpleCodecs;
private final Function<Type, Codec<?>> preferredBoskCodecs;
private final Function<Reference<?>, SerializationPlugin.DeserializationScope> deserializationScopeFunction;
public final class Formatter extends BsonFormatter {

/**
* If the diagnostic attributes are identical from one update to the next,
Expand All @@ -66,12 +56,7 @@ public final class Formatter extends BsonSurgeonFormatter {
private volatile MapValue<String> lastEventDiagnosticAttributes = MapValue.empty();

public Formatter(BoskInfo<?> boskInfo, BsonPlugin bsonPlugin) {
this.simpleCodecs = CodecRegistries.fromProviders(
bsonPlugin.codecProviderFor(boskInfo),
new ValueCodecProvider(),
new DocumentCodecProvider());
this.preferredBoskCodecs = type -> bsonPlugin.getCodec(type, rawClass(type), simpleCodecs, boskInfo);
this.deserializationScopeFunction = bsonPlugin::newDeserializationScope;
super(boskInfo, bsonPlugin);
}

/**
Expand Down Expand Up @@ -103,19 +88,6 @@ public Formatter(BoskInfo<?> boskInfo, BsonPlugin bsonPlugin) {
// Helpers to translate Bosk <-> MongoDB
//

Codec<?> codecFor(Type type) {
// BsonPlugin gives better codecs than CodecRegistry, because BsonPlugin is aware of generics,
// so we always try that first. The CodecSupplier protocol uses "null" to indicate that another
// CodecSupplier should be used, so we follow that protocol and fall back on the CodecRegistry.
// TODO: Should this logic be in BsonPlugin? It has nothing to do with MongoDriver really.
Codec<?> result = preferredBoskCodecs.apply(type);
if (result == null) {
return simpleCodecs.get(rawClass(type));
} else {
return result;
}
}

@SuppressWarnings("unchecked")
<T> T document2object(BsonDocument doc, Reference<T> target) {
Type type = target.targetType();
Expand Down Expand Up @@ -187,42 +159,6 @@ MapValue<String> decodeDiagnosticAttributes(BsonDocument diagnostics) {
return result;
}

/**
* @see #bsonValue2object(BsonValue, Reference)
*/
@SuppressWarnings("unchecked")
public <T> BsonValue object2bsonValue(T object, Type type) {
rawClass(type).cast(object);
Codec<T> objectCodec = (Codec<T>) codecFor(type);
BsonDocument document = new BsonDocument();
try (BsonDocumentWriter writer = new BsonDocumentWriter(document)) {
// To support arbitrary values, not just whole documents, we put the result INSIDE a document.
writer.writeStartDocument();
writer.writeName("value");
objectCodec.encode(writer, object, EncoderContext.builder().build());
writer.writeEndDocument();
}
return document.get("value");
}

/**
* @see #object2bsonValue(Object, Type)
*/
@SuppressWarnings("unchecked")
<T> T bsonValue2object(BsonValue bson, Reference<T> target) {
Codec<T> objectCodec = (Codec<T>) codecFor(target.targetType());
BsonDocument document = new BsonDocument();
document.append("value", bson);
try (
@SuppressWarnings("unused") SerializationPlugin.DeserializationScope scope = deserializationScopeFunction.apply(target);
BsonReader reader = document.asBsonReader()
) {
reader.readStartDocument();
reader.readName("value");
return objectCodec.decode(reader, DecoderContext.builder().build());
}
}

@SuppressWarnings("unchecked")
long bsonValueBinarySize(BsonValue bson) {
Codec<BsonValue> codec = new BsonValueCodec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import works.bosk.Reference;
import works.bosk.StateTreeNode;
import works.bosk.bson.BsonPlugin;
import works.bosk.bson.BsonSurgeonFormatter.DocumentFields;
import works.bosk.bson.BsonFormatter.DocumentFields;
import works.bosk.drivers.mongo.MappedDiagnosticContext.MDCScope;
import works.bosk.drivers.mongo.MongoDriverSettings.DatabaseFormat;
import works.bosk.drivers.mongo.MongoDriverSettings.InitialDatabaseUnavailableMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import works.bosk.Reference;
import works.bosk.StateTreeNode;
import works.bosk.bson.BsonPlugin;
import works.bosk.bson.BsonSurgeonFormatter.DocumentFields;
import works.bosk.bson.BsonFormatter.DocumentFields;
import works.bosk.exceptions.FlushFailureException;
import works.bosk.exceptions.InvalidTypeException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import works.bosk.Reference;
import works.bosk.TestEntityBuilder;
import works.bosk.bson.BsonPlugin;
import works.bosk.bson.BsonSurgeonFormatter;
import works.bosk.bson.BsonFormatter;
import works.bosk.exceptions.InvalidTypeException;
import works.bosk.util.Types;

Expand Down Expand Up @@ -72,7 +72,7 @@ void object2bsonValue() {
)
;

ArrayList<String> dottedName = BsonSurgeonFormatter.dottedFieldNameSegments(weirdRef, weirdRef.path().length(), bosk.rootReference());
ArrayList<String> dottedName = BsonFormatter.dottedFieldNameSegments(weirdRef, weirdRef.path().length(), bosk.rootReference());
BsonDocument expected = new BsonDocument()
.append(dottedName.get(dottedName.size()-1), weirdDoc);
assertEquals(expected, actual);
Expand Down

0 comments on commit dfbf8e2

Please sign in to comment.