This is an automated email from the ASF dual-hosted git repository. Cole-Greer pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit a0c04d2139787baaa0b4ce1d4b07a891e2bf8088 Merge: 71d8528449 f33bbcb13b Author: Cole Greer <[email protected]> AuthorDate: Thu Aug 27 13:04:21 2026 -0700 Merge branch '3.8-dev' .beads/issues.jsonl | 11 +++ CHANGELOG.asciidoc | 1 + .../tinkergraph/structure/TinkerIoRegistryV2.java | 27 +++++- .../tinkergraph/structure/TinkerIoRegistryV3.java | 27 +++++- .../tinkergraph/structure/TinkerIoRegistryV4.java | 27 +++++- .../TinkerGraphGraphSONSerializerV2Test.java | 36 +++++++ .../TinkerGraphGraphSONSerializerV3Test.java | 107 +++++++++++++++++++++ .../TinkerGraphGraphSONSerializerV4Test.java | 107 +++++++++++++++++++++ 8 files changed, 334 insertions(+), 9 deletions(-) diff --cc tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java index d151324ad7,dbe1874abb..c031880e02 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV2.java @@@ -187,14 -188,16 +188,16 @@@ public final class TinkerIoRegistryV2 e } @Override - public TinkerGraph deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + public TinkerMemoryGraph deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { final Configuration conf = new BaseConfiguration(); conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", "list"); - final TinkerGraph graph = TinkerGraph.open(conf); + final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf); - while (jsonParser.nextToken() != JsonToken.END_OBJECT) { + while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) { if (jsonParser.getCurrentName().equals("vertices")) { - while (jsonParser.nextToken() != JsonToken.END_ARRAY) { + if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY) + throw new JsonParseException(jsonParser, "Expected an array value for the \"vertices\" field"); + while (nextTokenOrThrow(jsonParser) != JsonToken.END_ARRAY) { if (jsonParser.currentToken() == JsonToken.START_OBJECT) { final DetachedVertex v = (DetachedVertex) deserializationContext.readValue(jsonParser, Vertex.class); v.attach(Attachable.Method.getOrCreate(graph)); diff --cc tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java index 435432ea6d,6252f1c867..3dacd175ed --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV3.java @@@ -187,14 -188,16 +188,16 @@@ public final class TinkerIoRegistryV3 e } @Override - public TinkerGraph deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + public TinkerMemoryGraph deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { final Configuration conf = new BaseConfiguration(); conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", "list"); - final TinkerGraph graph = TinkerGraph.open(conf); + final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf); - while (jsonParser.nextToken() != JsonToken.END_OBJECT) { + while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) { if (jsonParser.getCurrentName().equals("vertices")) { - while (jsonParser.nextToken() != JsonToken.END_ARRAY) { + if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY) + throw new JsonParseException(jsonParser, "Expected an array value for the \"vertices\" field"); + while (nextTokenOrThrow(jsonParser) != JsonToken.END_ARRAY) { if (jsonParser.currentToken() == JsonToken.START_OBJECT) { final DetachedVertex v = (DetachedVertex) deserializationContext.readValue(jsonParser, Vertex.class); v.attach(Attachable.Method.getOrCreate(graph)); diff --cc tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java index 3e39378983,0000000000..a4dbe7ea00 mode 100644,000000..100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIoRegistryV4.java @@@ -1,216 -1,0 +1,237 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.tinkergraph.structure; + +import org.apache.commons.configuration2.BaseConfiguration; +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.io.AbstractIoRegistry; +import org.apache.tinkerpop.gremlin.structure.io.GraphReader; +import org.apache.tinkerpop.gremlin.structure.io.GraphWriter; +import org.apache.tinkerpop.gremlin.structure.io.IoRegistry; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONIo; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONTokens; +import org.apache.tinkerpop.gremlin.structure.io.graphson.TinkerPopJacksonModule; +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoIo; +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoReader; +import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoWriter; +import org.apache.tinkerpop.gremlin.structure.util.Attachable; +import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedEdge; +import org.apache.tinkerpop.gremlin.structure.util.detached.DetachedVertex; +import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator; ++import org.apache.tinkerpop.shaded.jackson.core.JsonParseException; +import org.apache.tinkerpop.shaded.jackson.core.JsonParser; +import org.apache.tinkerpop.shaded.jackson.core.JsonProcessingException; +import org.apache.tinkerpop.shaded.jackson.core.JsonToken; +import org.apache.tinkerpop.shaded.jackson.databind.DeserializationContext; +import org.apache.tinkerpop.shaded.jackson.databind.SerializerProvider; +import org.apache.tinkerpop.shaded.jackson.databind.deser.std.StdDeserializer; +import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdScalarSerializer; +import org.apache.tinkerpop.shaded.kryo.Kryo; +import org.apache.tinkerpop.shaded.kryo.Serializer; +import org.apache.tinkerpop.shaded.kryo.io.Input; +import org.apache.tinkerpop.shaded.kryo.io.Output; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * An implementation of the {@link IoRegistry} interface that provides serializers with custom configurations for + * implementation specific classes that might need to be serialized. This registry allows a {@link TinkerMemoryGraph} to + * be serialized directly which is useful for moving small graphs around on the network. + * <p/> + * Most providers need not implement this kind of custom serializer as they will deal with much larger graphs that + * wouldn't be practical to serialize in this fashion. This is a bit of a special case for TinkerGraph given its + * in-memory status. Typical implementations would create serializers for a complex vertex identifier or a + * custom data class like a "geographic point". + * + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public final class TinkerIoRegistryV4 extends AbstractIoRegistry { + + private static final TinkerIoRegistryV4 INSTANCE = new TinkerIoRegistryV4(); + + private TinkerIoRegistryV4() { + register(GryoIo.class, TinkerMemoryGraph.class, new TinkerGraphGryoSerializer()); + register(GraphSONIo.class, null, new TinkerModuleV2()); + } + + public static TinkerIoRegistryV4 instance() { + return INSTANCE; + } + + /** + * Provides a method to serialize an entire {@link TinkerMemoryGraph} into itself for Gryo. This is useful when + * shipping small graphs around through Gremlin Server. Reuses the existing Kryo instance for serialization. + */ + final static class TinkerGraphGryoSerializer extends Serializer<TinkerMemoryGraph> { + @Override + public void write(final Kryo kryo, final Output output, final TinkerMemoryGraph graph) { + try (final ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + GryoWriter.build().mapper(() -> kryo).create().writeGraph(stream, graph); + final byte[] bytes = stream.toByteArray(); + output.writeInt(bytes.length); + output.write(bytes); + } catch (Exception io) { + throw new RuntimeException(io); + } + } + + @Override + public TinkerMemoryGraph read(final Kryo kryo, final Input input, final Class<TinkerMemoryGraph> tinkerGraphClass) { + final Configuration conf = new BaseConfiguration(); + conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", "list"); + final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf); + final int len = input.readInt(); + final byte[] bytes = input.readBytes(len); + try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) { + GryoReader.build().mapper(() -> kryo).create().readGraph(stream, graph); + } catch (Exception io) { + throw new RuntimeException(io); + } + + return graph; + } + } + + /** + * Provides a method to serialize an entire {@link TinkerMemoryGraph} into itself for GraphSON. This is useful when + * shipping small graphs around through Gremlin Server. + */ + final static class TinkerModuleV2 extends TinkerPopJacksonModule { + public TinkerModuleV2() { + super("tinkergraph-2.0"); + addSerializer(TinkerMemoryGraph.class, new TinkerGraphJacksonSerializer()); + addDeserializer(TinkerMemoryGraph.class, new TinkerGraphJacksonDeserializer()); + } + + @Override + public Map<Class, String> getTypeDefinitions() { + return new HashMap<Class, String>(){{ + put(TinkerMemoryGraph.class, "graph"); + }}; + } + + @Override + public String getTypeNamespace() { + return GraphSONTokens.GREMLIN_TYPE_NAMESPACE; + } + } + + /** + * Serializes the graph into an edge list format. Edge list is a better choices than adjacency list (which is + * typically standard from the {@link GraphReader} and {@link GraphWriter} perspective) in this case because + * the use case for this isn't around massive graphs. The use case is for "small" subgraphs that are being + * shipped over the wire from Gremlin Server. Edge list format is a bit easier for non-JVM languages to work + * with as a format and doesn't require a cache for loading (as vertex labels are not serialized in adjacency + * list). + */ + final static class TinkerGraphJacksonSerializer extends StdScalarSerializer<TinkerMemoryGraph> { + + public TinkerGraphJacksonSerializer() { + super(TinkerMemoryGraph.class); + } + + @Override + public void serialize(final TinkerMemoryGraph graph, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) + throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeFieldName(GraphSONTokens.VERTICES); + jsonGenerator.writeStartArray(); + + final Iterator<Vertex> vertices = graph.vertices(); + while (vertices.hasNext()) { + serializerProvider.defaultSerializeValue(vertices.next(), jsonGenerator); + } + + jsonGenerator.writeEndArray(); + jsonGenerator.writeFieldName(GraphSONTokens.EDGES); + jsonGenerator.writeStartArray(); + + final Iterator<Edge> edges = graph.edges(); + while (edges.hasNext()) { + serializerProvider.defaultSerializeValue(edges.next(), jsonGenerator); + } + + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + } + } + + /** + * Deserializes the edge list format. + */ + static class TinkerGraphJacksonDeserializer extends StdDeserializer<TinkerMemoryGraph> { + public TinkerGraphJacksonDeserializer() { + super(TinkerMemoryGraph.class); + } + + @Override + public TinkerMemoryGraph deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final Configuration conf = new BaseConfiguration(); + conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality", "list"); + final TinkerMemoryGraph graph = TinkerMemoryGraph.open(conf); + - while (jsonParser.nextToken() != JsonToken.END_OBJECT) { ++ while (nextTokenOrThrow(jsonParser) != JsonToken.END_OBJECT) { + if (jsonParser.getCurrentName().equals("vertices")) { - while (jsonParser.nextToken() != JsonToken.END_ARRAY) { ++ if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY) ++ throw new JsonParseException(jsonParser, "Expected an array value for the \"vertices\" field"); ++ while (nextTokenOrThrow(jsonParser) != JsonToken.END_ARRAY) { + if (jsonParser.currentToken() == JsonToken.START_OBJECT) { + final DetachedVertex v = (DetachedVertex) deserializationContext.readValue(jsonParser, Vertex.class); + v.attach(Attachable.Method.getOrCreate(graph)); + } + } + } else if (jsonParser.getCurrentName().equals("edges")) { - while (jsonParser.nextToken() != JsonToken.END_ARRAY) { ++ if (nextTokenOrThrow(jsonParser) != JsonToken.START_ARRAY) ++ throw new JsonParseException(jsonParser, "Expected an array value for the \"edges\" field"); ++ while (nextTokenOrThrow(jsonParser) != JsonToken.END_ARRAY) { + if (jsonParser.currentToken() == JsonToken.START_OBJECT) { + final DetachedEdge e = (DetachedEdge) deserializationContext.readValue(jsonParser, Edge.class); + e.attach(Attachable.Method.getOrCreate(graph)); + } + } + } + } + + return graph; + } ++ ++ /** ++ * Advances the parser one token, treating end-of-input as a parse error. Once the ++ * underlying input is exhausted, {@code JsonParser.nextToken()} returns {@code null} on ++ * every subsequent call rather than throwing. A loop whose only exit condition compares ++ * the result against a structural close token (e.g. {@code END_ARRAY}) would therefore ++ * never terminate on truncated or malformed input. This wrapper converts the {@code null} ++ * return into a {@code JsonParseException} so that callers can use a simple while-loop ++ * idiom without risk of non-termination. ++ */ ++ private static JsonToken nextTokenOrThrow(final JsonParser jsonParser) throws IOException { ++ final JsonToken token = jsonParser.nextToken(); ++ if (null == token) ++ throw new JsonParseException(jsonParser, "Unexpected end-of-input while reading a TinkerGraph"); ++ return token; ++ } + } +} diff --cc tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java index 67c986f498,b795764f20..32eb7d38a6 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV2Test.java @@@ -338,6 -338,42 +338,42 @@@ public class TinkerGraphGraphSONSeriali } } + @Test(timeout = 5000) + public void shouldFailFastOnScalarVerticesField() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":0}}"; + final GraphReader reader = getReader(defaultMapperV2); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for malformed tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — the START_ARRAY check threw as intended + } + } + + @Test(timeout = 5000) + public void shouldFailFastOnScalarEdgesField() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}"; + final GraphReader reader = getReader(defaultMapperV2); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for malformed tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — the START_ARRAY check threw as intended + } + } + + @Test(timeout = 5000) + public void shouldFailFastOnTruncatedInput() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":["; + final GraphReader reader = getReader(defaultMapperV2); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for truncated tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — nextTokenOrThrow detected end-of-input + } + } + @Test public void deserializersTestsProperty() { final TinkerGraph tg = TinkerGraph.open(); diff --cc tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java index 0000000000,727985ee8f..f00fa89dcf mode 000000,100644..100644 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java @@@ -1,0 -1,107 +1,107 @@@ + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.apache.tinkerpop.gremlin.tinkergraph.structure; + + import org.apache.tinkerpop.gremlin.structure.io.GraphReader; + import org.apache.tinkerpop.gremlin.structure.io.GraphWriter; + import org.apache.tinkerpop.gremlin.structure.io.Mapper; + import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; + import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader; + import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; + import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter; + import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV3; + import org.junit.Test; + + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; + import java.io.IOException; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.fail; + + /** + * Serialization and deserialization tests for GraphSON V3 with the TinkerGraph custom type. + */ + public class TinkerGraphGraphSONSerializerV3Test { + + private final Mapper defaultMapperV3 = GraphSONMapper.build() + .version(GraphSONVersion.V3_0) + .addCustomModule(GraphSONXModuleV3.build()) + .addRegistry(TinkerIoRegistryV3.instance()) + .create(); + + @Test + public void shouldDeserializeWellFormedGraph() throws IOException { - final TinkerGraph original = TinkerFactory.createModern(); ++ final TinkerMemoryGraph original = TinkerFactory.createModern(); + final GraphWriter writer = getWriter(defaultMapperV3); + final GraphReader reader = getReader(defaultMapperV3); + + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + writer.writeObject(out, original); + final String json = out.toString(); - final TinkerGraph read = reader.readObject(new ByteArrayInputStream(json.getBytes()), TinkerGraph.class); ++ final TinkerMemoryGraph read = reader.readObject(new ByteArrayInputStream(json.getBytes()), TinkerMemoryGraph.class); + assertEquals(6L, read.traversal().V().count().next().longValue()); + assertEquals(6L, read.traversal().E().count().next().longValue()); + } + } + + @Test(timeout = 5000) + public void shouldFailFastOnScalarVerticesField() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":0}}"; + final GraphReader reader = getReader(defaultMapperV3); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for malformed tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — the START_ARRAY check threw as intended + } + } + + @Test(timeout = 5000) + public void shouldFailFastOnScalarEdgesField() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}"; + final GraphReader reader = getReader(defaultMapperV3); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for malformed tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — the START_ARRAY check threw as intended + } + } + + @Test(timeout = 5000) + public void shouldFailFastOnTruncatedInput() throws IOException { + final String malformed = "{\"@type\":\"tinker:graph\",\"@value\":{\"vertices\":["; + final GraphReader reader = getReader(defaultMapperV3); + try { - reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerGraph.class); ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); + fail("Expected IOException for truncated tinker:graph input"); + } catch (IOException expected) { + // JsonParseException — nextTokenOrThrow detected end-of-input + } + } + + private GraphWriter getWriter(Mapper paramMapper) { + return GraphSONWriter.build().mapper(paramMapper).create(); + } + + private GraphReader getReader(Mapper paramMapper) { + return GraphSONReader.build().mapper(paramMapper).create(); + } + } diff --cc tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV4Test.java index 0000000000,0000000000..7b2c012879 new file mode 100644 --- /dev/null +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV4Test.java @@@ -1,0 -1,0 +1,107 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.tinkerpop.gremlin.tinkergraph.structure; ++ ++import org.apache.tinkerpop.gremlin.structure.io.GraphReader; ++import org.apache.tinkerpop.gremlin.structure.io.GraphWriter; ++import org.apache.tinkerpop.gremlin.structure.io.Mapper; ++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper; ++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONReader; ++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; ++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter; ++import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONXModuleV4; ++import org.junit.Test; ++ ++import java.io.ByteArrayInputStream; ++import java.io.ByteArrayOutputStream; ++import java.io.IOException; ++ ++import static org.junit.Assert.assertEquals; ++import static org.junit.Assert.fail; ++ ++/** ++ * Serialization and deserialization tests for GraphSON V4 with the TinkerGraph custom type. ++ */ ++public class TinkerGraphGraphSONSerializerV4Test { ++ ++ private final Mapper defaultMapperV4 = GraphSONMapper.build() ++ .version(GraphSONVersion.V4_0) ++ .addCustomModule(GraphSONXModuleV4.build()) ++ .addRegistry(TinkerIoRegistryV4.instance()) ++ .create(); ++ ++ @Test ++ public void shouldDeserializeWellFormedGraph() throws IOException { ++ final TinkerMemoryGraph original = TinkerFactory.createModern(); ++ final GraphWriter writer = getWriter(defaultMapperV4); ++ final GraphReader reader = getReader(defaultMapperV4); ++ ++ try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { ++ writer.writeObject(out, original); ++ final String json = out.toString(); ++ final TinkerMemoryGraph read = reader.readObject(new ByteArrayInputStream(json.getBytes()), TinkerMemoryGraph.class); ++ assertEquals(6L, read.traversal().V().count().next().longValue()); ++ assertEquals(6L, read.traversal().E().count().next().longValue()); ++ } ++ } ++ ++ @Test(timeout = 5000) ++ public void shouldFailFastOnScalarVerticesField() throws IOException { ++ final String malformed = "{\"@type\":\"g:graph\",\"@value\":{\"vertices\":0}}"; ++ final GraphReader reader = getReader(defaultMapperV4); ++ try { ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); ++ fail("Expected IOException for malformed g:graph input"); ++ } catch (IOException expected) { ++ // JsonParseException — the START_ARRAY check threw as intended ++ } ++ } ++ ++ @Test(timeout = 5000) ++ public void shouldFailFastOnScalarEdgesField() throws IOException { ++ final String malformed = "{\"@type\":\"g:graph\",\"@value\":{\"vertices\":[],\"edges\":0}}"; ++ final GraphReader reader = getReader(defaultMapperV4); ++ try { ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); ++ fail("Expected IOException for malformed g:graph input"); ++ } catch (IOException expected) { ++ // JsonParseException — the START_ARRAY check threw as intended ++ } ++ } ++ ++ @Test(timeout = 5000) ++ public void shouldFailFastOnTruncatedInput() throws IOException { ++ final String malformed = "{\"@type\":\"g:graph\",\"@value\":{\"vertices\":["; ++ final GraphReader reader = getReader(defaultMapperV4); ++ try { ++ reader.readObject(new ByteArrayInputStream(malformed.getBytes()), TinkerMemoryGraph.class); ++ fail("Expected IOException for truncated g:graph input"); ++ } catch (IOException expected) { ++ // JsonParseException — nextTokenOrThrow detected end-of-input ++ } ++ } ++ ++ private GraphWriter getWriter(Mapper paramMapper) { ++ return GraphSONWriter.build().mapper(paramMapper).create(); ++ } ++ ++ private GraphReader getReader(Mapper paramMapper) { ++ return GraphSONReader.build().mapper(paramMapper).create(); ++ } ++}
