This is an automated email from the ASF dual-hosted git repository.
Cole-Greer pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.7-dev by this push:
new 1f0ded919d CTR: Guard against non-terminating loops in TinkerGraph
GraphSON deserialization
1f0ded919d is described below
commit 1f0ded919d0236123110d02e23f63b4bcb356e1e
Author: Cole Greer <[email protected]>
AuthorDate: Thu Aug 20 11:23:48 2026 -0700
CTR: Guard against non-terminating loops in TinkerGraph GraphSON
deserialization
Assisted-by: Kiro:claude-opus-5
---
.beads/issues.jsonl | 11 +++
CHANGELOG.asciidoc | 1 +
.../tinkergraph/structure/TinkerIoRegistryV2.java | 27 +++++-
.../tinkergraph/structure/TinkerIoRegistryV3.java | 27 +++++-
.../TinkerGraphGraphSONSerializerV2Test.java | 36 +++++++
.../TinkerGraphGraphSONSerializerV3Test.java | 107 +++++++++++++++++++++
6 files changed, 203 insertions(+), 6 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
new file mode 100644
index 0000000000..2fda10a578
--- /dev/null
+++ b/.beads/issues.jsonl
@@ -0,0 +1,11 @@
+{"_type":"issue","id":"tinkerpop-usp.2","title":"Phase 1: Scaffold
workflow_dispatch skeleton + core Java build","description":"Create
.github/workflows/release-preflight.yml — the minimal \"Run workflow\" button
that builds the Java core and produces a summary.\n\n**Files:**
.github/workflows/release-preflight.yml (create)\n\n**Context:**\n- Model on
build-test.yml triggers/setup. workflow_dispatch with inputs appears as a \"Run
workflow\" button.\n- Core build mirrors doc step 5a: mvn [...]
+{"_type":"issue","id":"tinkerpop-usp.1","title":"Phase 0: Prerequisite —
remove mono from .NET packaging (TINKERPOP-2085)","description":"Remove the
mono dependency from the .NET template packaging step so the pre-flight
dotnet-template job runs cleanly on GitHub-hosted runners without
mono.\n\n**Files:** gremlin-dotnet/src/pom.xml (edit), docker/Dockerfile
(edit), docs/src/dev/developer/release.asciidoc (edit if commands
change)\n\n**Context:**\n- The only mono usage is the Ant exec at [...]
+{"_type":"issue","id":"tinkerpop-usp.6","title":"Phase 5: License/NOTICE (RAT)
+ javadoc generation + upgrade-prompt existence (doc steps 5f, 5g,
6)","description":"Add a static-checks job for license-header validation (RAT),
javadoc build verification, and upgrade-prompt existence check.\n\n**Files:**
.github/workflows/release-preflight.yml (edit)\n\n**Context:**\n-
apache-rat-plugin configured at pom.xml:463 — mvn apache-rat:check flags
missing/incorrect license headers (partial covera [...]
+{"_type":"issue","id":"tinkerpop-usp","title":"Release Pre-Flight Check
Automation","description":"Encode the entire pre-flight check from
docs/src/dev/developer/release.asciidoc into a single workflow_dispatch GitHub
Actions button at .github/workflows/release-preflight.yml. Heavy
non-side-effecting verification (integration tests, docs generation, dependency
reports) runs on every press. Only genuinely side-effecting steps (SNAPSHOT
deploy to Apache Nexus, docs publish to SVN) are gate [...]
+{"_type":"issue","id":"tinkerpop-usp.10","title":"Phase 9: Aggregate
pre-flight report + manual checklist","description":"Complete the summary job
(stubbed in Phase 1) so a single run produces a clear pass/fail dashboard,
links the Phase 8 human-assist artifacts, and lists the residual
non-automatable checklist.\n\n**Files:**
.github/workflows/release-preflight.yml (edit)\n\n**Context:**\n- The summary
job (needs: every job including Phase 8 jobs, if: always()) reads each job
result via [...]
+{"_type":"issue","id":"tinkerpop-usp.4","title":"Phase 3: Docker-image build +
Gremlin.Net.Template packaging (doc steps 5c, 5d)","description":"Add jobs to
build Docker images (gremlin-server, gremlin-console) and validate
Gremlin.Net.Template .nupkg packaging — both non-side-effecting (no
push/publish at install phase).\n\n**Files:**
.github/workflows/release-preflight.yml (edit)\n\n**Context:**\n- Step 5c: mvn
clean install -pl gremlin-server,gremlin-console -DdockerImages. The docker
[...]
+{"_type":"issue","id":"tinkerpop-usp.3","title":"Phase 2: Dependency-update
report (doc step 3)","description":"Add a dependency-report job that runs on
every press (non-gated, non-failing) and surfaces available minor/patch updates
across all ecosystem toolchains.\n\n**Files:**
.github/workflows/release-preflight.yml (edit)\n\n**Context:**\n- Doc step 3
lists four tools: mvn versions:display-dependency-updates
-DallowMajorUpdates=false, go list -m -u all, npm-check-updates, dotnet-check
[...]
+{"_type":"issue","id":"tinkerpop-usp.9","title":"Phase 8: Human-assist —
decision-support data + pre-populated email drafts (doc steps 2, 4, 5f, 5h,
6)","description":"Add jobs that gather decision-support data and pre-populate
email drafts for the human/comms steps that cannot be fully
automated.\n\n**Files:** .github/workflows/release-preflight.yml (edit);
optionally bin/preflight-emails.sh (new helper) and email-draft
templates\n\n**Context:**\nTwo kinds of assist:\n1. Decision-suppor [...]
+{"_type":"issue","id":"tinkerpop-usp.7","title":"Phase 6: Documentation
generation validation (doc step 7, non-publishing) — heavy, runs every
press","description":"Add a docs-validation job that runs bin/process-docs.sh
(generation only, no SVN publish). Non-side-effecting, runs on every press per
the exhaustive pre-flight rationale.\n\n**Files:**
.github/workflows/release-preflight.yml (edit)\n\n**Context:**\n- Step 7
ultimately runs bin/publish-docs.sh (publishes to SVN). The validati [...]
+{"_type":"issue","id":"tinkerpop-usp.5","title":"Phase 4: Full integration
test suite (doc step 5b) — heavy, runs every press","description":"Add the full
dockerized integration test suite job. Runs on every press (not gated) per the
exhaustive pre-flight rationale — pre-flight runs rarely and every verification
step is necessary.\n\n**Files:** .github/workflows/release-preflight.yml
(edit)\n\n**Context:**\n- Step 5b: docker/build.sh -t -i. Builds base images
and runs the full integratio [...]
+{"_type":"issue","id":"tinkerpop-usp.8","title":"Phase 7: Side-effecting
stages — SNAPSHOT deploy + docs publish (gated + secrets)
[HIGH-RISK]","description":"⚠️ HIGH-RISK / SIDE-EFFECTING. These push to shared
Apache infrastructure. Default OFF. Require secrets and explicit input
toggles.\n\nAdd gated jobs for SNAPSHOT deploy (5e) and docs publish (7).
Follow Log4j secret-injection pattern (setup-java
server-id/server-username/server-password, gpg-private-key; svn
non-interactive).\n\n* [...]
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 1505c29d7f..b6865fa065 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -29,6 +29,7 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Disabled unsafe Java deserialization on the Gryo IO paths and added
`GryoMapper.Builder.javaSerializationAllowed(boolean)` to control it.
* Added verification to `io()` ensuring a class named by `IO.reader`,
`IO.writer` or `IO.registry` implements the expected interface before loading
and initializing it.
* Fixed `io().write()` to resolve its `GraphWriter` before opening the output
file, so that a writer which cannot be constructed no longer truncates the
target.
+* Fixed `TinkerGraph` GraphSON deserialization to fail fast with
`JsonParseException` on malformed `tinker:graph` input.
* Fixed `subgraph()` to throw a descriptive error identifying the required
`Edge` input instead of an internal `ClassCastException` when the traversal
produces a non-edge value.
* Fixed `where(P)` to throw a descriptive error identifying the required
String scope key (and suggesting `is(P)` for value comparisons) instead of an
internal `ClassCastException` when given a non-String predicate value.
* Fixed `PeerPressure.property_name` in `gremlin-python` incorrectly mapping
to the `pageRank` property name token.
diff --git
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
index 9b97e7aa75..dbe1874abb 100644
---
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
@@ -36,6 +36,7 @@ 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;
@@ -192,16 +193,20 @@ public final class TinkerIoRegistryV2 extends
AbstractIoRegistry {
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality",
"list");
final TinkerGraph graph = TinkerGraph.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));
@@ -213,6 +218,22 @@ public final class TinkerIoRegistryV2 extends
AbstractIoRegistry {
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;
+ }
+
@Override
public boolean isCachable() {
return true;
diff --git
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
index 7783a5c0fc..6252f1c867 100644
---
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
@@ -36,6 +36,7 @@ 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;
@@ -192,16 +193,20 @@ public final class TinkerIoRegistryV3 extends
AbstractIoRegistry {
conf.setProperty("gremlin.tinkergraph.defaultVertexPropertyCardinality",
"list");
final TinkerGraph graph = TinkerGraph.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));
@@ -212,5 +217,21 @@ public final class TinkerIoRegistryV3 extends
AbstractIoRegistry {
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 --git
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
index f65e8b70ee..b795764f20 100644
---
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 @@ public class TinkerGraphGraphSONSerializerV2Test {
}
}
+ @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);
+ 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);
+ 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);
+ 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 --git
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
new file mode 100644
index 0000000000..727985ee8f
--- /dev/null
+++
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphGraphSONSerializerV3Test.java
@@ -0,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.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 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);
+ 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);
+ 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);
+ 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);
+ 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();
+ }
+}