/* * 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.structure.io.graphson; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.Pop; import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions; import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent; import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics; import org.apache.tinkerpop.gremlin.structure.Column; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.util.function.HashMapSupplier; import org.apache.tinkerpop.gremlin.util.function.Lambda; import org.apache.tinkerpop.shaded.jackson.annotation.JsonTypeInfo; import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator; import org.apache.tinkerpop.shaded.jackson.databind.BeanProperty; import org.apache.tinkerpop.shaded.jackson.databind.jsontype.TypeIdResolver; import org.apache.tinkerpop.shaded.jackson.databind.jsontype.TypeSerializer; import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; /** * Extension of the Jackson's default TypeSerializer. An instance of this object will be passed to the serializers * on which they can safely call the utility methods to serialize types and making it compatible with the version * 2.0 of GraphSON. * * @author Kevin Gallardo (https://kgdo.me) */ public class GraphSONTypeSerializer extends TypeSerializer { private final TypeIdResolver idRes; private final String propertyName; private final TypeInfo typeInfo; private final String valuePropertyName; private final Map<Class, Class> classMap = new HashMap<>(); GraphSONTypeSerializer(final TypeIdResolver idRes, final String propertyName, final TypeInfo typeInfo, final String valuePropertyName) { this.idRes = idRes; this.propertyName = propertyName; this.typeInfo = typeInfo; this.valuePropertyName = valuePropertyName; } @Override public TypeSerializer forProperty(final BeanProperty beanProperty) { return this; } @Override public JsonTypeInfo.As getTypeInclusion() { return null; } @Override public String getPropertyName() { return propertyName; } @Override public TypeIdResolver getTypeIdResolver() { return idRes; } @Override public void writeTypePrefixForScalar(final Object o, final JsonGenerator jsonGenerator) throws IOException { if (canWriteTypeId()) { writeTypePrefix(jsonGenerator, getTypeIdResolver().idFromValueAndType(o, getClassFromObject(o))); } } @Override public void writeTypePrefixForObject(final Object o, final JsonGenerator jsonGenerator) throws IOException { jsonGenerator.writeStartObject(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypePrefix(Map); } @Override public void writeTypePrefixForArray(final Object o, final JsonGenerator jsonGenerator) throws IOException { jsonGenerator.writeStartArray(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypePrefix(List); } @Override public void writeTypeSuffixForScalar(final Object o, final JsonGenerator jsonGenerator) throws IOException { if (canWriteTypeId()) { writeTypeSuffix(jsonGenerator); } } @Override public void writeTypeSuffixForObject(final Object o, final JsonGenerator jsonGenerator) throws IOException { jsonGenerator.writeEndObject(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypeSuffix(Map); } @Override public void writeTypeSuffixForArray(final Object o, final JsonGenerator jsonGenerator) throws IOException { jsonGenerator.writeEndArray(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypeSuffix(List); } @Override public void writeCustomTypePrefixForScalar(final Object o, final JsonGenerator jsonGenerator, final String s) throws IOException { if (canWriteTypeId()) { writeTypePrefix(jsonGenerator, s); } } @Override public void writeCustomTypePrefixForObject(final Object o, final JsonGenerator jsonGenerator, final String s) throws IOException { jsonGenerator.writeStartObject(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypePrefix(s); } @Override public void writeCustomTypePrefixForArray(final Object o, final JsonGenerator jsonGenerator, final String s) throws IOException { jsonGenerator.writeStartArray(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypePrefix(s); } @Override public void writeCustomTypeSuffixForScalar(final Object o, final JsonGenerator jsonGenerator, final String s) throws IOException { if (canWriteTypeId()) { writeTypeSuffix(jsonGenerator); } } @Override public void writeCustomTypeSuffixForObject(final Object o, final JsonGenerator jsonGenerator, final String s) throws IOException { jsonGenerator.writeEndObject(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypeSuffix(s); } @Override public void writeCustomTypeSuffixForArray(final Object o, final JsonGenerator jsonGenerator,final String s) throws IOException { jsonGenerator.writeEndArray(); // TODO: FULL_TYPES should be implemented here as : if (fullTypesModeEnabled()) writeTypeSuffix(s); } private boolean canWriteTypeId() { return typeInfo != null && typeInfo == TypeInfo.PARTIAL_TYPES; } private void writeTypePrefix(final JsonGenerator jsonGenerator, final String s) throws IOException { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField(this.getPropertyName(), s); jsonGenerator.writeFieldName(this.valuePropertyName); } private void writeTypeSuffix(final JsonGenerator jsonGenerator) throws IOException { jsonGenerator.writeEndObject(); } /** * We force only **one** translation of a Java object to a domain specific object. i.e. users register typeIDs * and serializers/deserializers for the predefined types we have in the spec. Graph, Vertex, Edge, * VertexProperty, etc... And **not** their implementations (TinkerGraph, DetachedVertex, TinkerEdge, etc..) */ private Class getClassFromObject(final Object o) { final Class c = o.getClass(); if (classMap.containsKey(c)) return classMap.get(c); final Class mapped; if (Vertex.class.isAssignableFrom(c)) mapped = Vertex.class; else if (Edge.class.isAssignableFrom(c)) mapped = Edge.class; else if (Path.class.isAssignableFrom(c)) mapped = Path.class; else if (VertexProperty.class.isAssignableFrom(c)) mapped = VertexProperty.class; else if (Metrics.class.isAssignableFrom(c)) mapped = Metrics.class; else if (TraversalMetrics.class.isAssignableFrom(c)) mapped = TraversalMetrics.class; else if (Property.class.isAssignableFrom(c)) mapped = Property.class; else if (ByteBuffer.class.isAssignableFrom(c)) mapped = ByteBuffer.class; else if (InetAddress.class.isAssignableFrom(c)) mapped = InetAddress.class; else if (Traverser.class.isAssignableFrom(c)) mapped = Traverser.class; else if (Lambda.class.isAssignableFrom(c)) mapped = Lambda.class; else if (VertexProperty.Cardinality.class.isAssignableFrom(c)) mapped = VertexProperty.Cardinality.class; else if (Column.class.isAssignableFrom(c)) mapped = Column.class; else if (Direction.class.isAssignableFrom(c)) mapped = Direction.class; else if (Operator.class.isAssignableFrom(c)) mapped = Operator.class; else if (Order.class.isAssignableFrom(c)) mapped = Order.class; else if (Pop.class.isAssignableFrom(c)) mapped = Pop.class; else if (SackFunctions.Barrier.class.isAssignableFrom(c)) mapped = SackFunctions.Barrier.class; else if (TraversalOptionParent.Pick.class.isAssignableFrom(c)) mapped = TraversalOptionParent.Pick.class; else if (Scope.class.isAssignableFrom(c)) mapped = Scope.class; else if (T.class.isAssignableFrom(c)) mapped = T.class; else mapped = c; classMap.put(c, mapped); return mapped; } }