All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavemaker.commons.json.module.CircularLoopHandlingSerializer Maven / Gradle / Ivy

There is a newer version: 11.9.4
Show newest version
/**
 * Copyright (C) 2020 WaveMaker, Inc.
 * 

* Licensed 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 com.wavemaker.commons.json.module; import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.BeanSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.PropertyWriter; import com.fasterxml.jackson.databind.ser.ResolvableSerializer; import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase; import com.fasterxml.jackson.databind.util.NameTransformer; /** * @author Dilip Kumar * @since 3/10/18 */ public class CircularLoopHandlingSerializer extends JsonSerializer implements ResolvableSerializer, ContextualSerializer { private static final Logger LOGGER = LoggerFactory.getLogger(CircularLoopHandlingSerializer.class); /** * Stack to store object call stack and to determine cycling dependency during serialization. */ private static final ThreadLocal> objectReferenceStackTL = new ThreadLocal<>(); private final BeanSerializer delegate; private final boolean failOnCircularReferences; public CircularLoopHandlingSerializer(final BeanSerializer delegate, final boolean failOnCircularReferences) { this.delegate = delegate; this.failOnCircularReferences = failOnCircularReferences; } @Override public void serialize( final T value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException { LOGGER.debug("Serialization started for :{}", value.getClass()); if (hasCyclicReference(value) && !delegate.usesObjectId()) { handleCyclicReference(gen, serializers); return; } notifyStartSerialization(value); try { delegate.serialize(value, gen, serializers); } finally { LOGGER.debug("Serialization completed for:{}", value.getClass()); notifyEndSerialization(); } } @Override public JsonSerializer createContextual( final SerializerProvider prov, final BeanProperty property) throws JsonMappingException { final JsonSerializer contextual = delegate.createContextual(prov, property); return wrapIfNeeded(contextual); } @Override public void resolve(final SerializerProvider provider) throws JsonMappingException { delegate.resolve(provider); } @Override public JsonSerializer unwrappingSerializer(final NameTransformer unwrapper) { return (JsonSerializer) wrapIfNeeded(delegate.unwrappingSerializer(unwrapper)); } @Override public JsonSerializer replaceDelegatee(final JsonSerializer delegatee) { return (JsonSerializer) wrapIfNeeded(delegate.replaceDelegatee(delegatee)); } @Override public JsonSerializer withFilterId(final Object filterId) { return wrapIfNeeded(delegate.withFilterId(filterId)); } @Override public void serializeWithType( final T value, final JsonGenerator gen, final SerializerProvider serializers, final TypeSerializer typeSer) throws IOException { delegate.serializeWithType(value, gen, serializers, typeSer); } @Override public Class handledType() { return (Class) delegate.handledType(); } @Override public boolean isEmpty(final T value) { return delegate.isEmpty(value); } @Override public boolean isEmpty(final SerializerProvider provider, final T value) { return delegate.isEmpty(provider, value); } @Override public boolean usesObjectId() { return delegate.usesObjectId(); } @Override public boolean isUnwrappingSerializer() { return delegate.isUnwrappingSerializer(); } @Override public JsonSerializer getDelegatee() { return delegate.getDelegatee(); } @Override public Iterator properties() { return delegate.properties(); } @Override public void acceptJsonFormatVisitor( final JsonFormatVisitorWrapper visitor, final JavaType type) throws JsonMappingException { delegate.acceptJsonFormatVisitor(visitor, type); } public void notifyStartSerialization(Object value) { getObjectRefStack().push(value); } /** * Removes the last added object from the stack. */ public void notifyEndSerialization() { getObjectRefStack().pop(); if (getObjectRefStack().isEmpty()) { objectReferenceStackTL.remove(); // gc } } /** * Check for the cycle in object serialization stack. It ignores Self references as they are handled separately. * * @param value to be check for cycle. * @return true when value in serialization reference stack and not as top value else false */ public boolean hasCyclicReference(Object value) { // ignores self references, return (!getObjectRefStack().isEmpty() && !getObjectRefStack().getFirst().equals(value)) && getObjectRefStack().contains(value); } public void handleCyclicReference(final JsonGenerator jgen, SerializerProvider provider) throws IOException { String refStack = printableRefStack(); if (failOnCircularReferences) { throw new JsonMappingException(jgen, "Cyclic-reference leading to cycle, Object Reference Stack:" + refStack); } // else serializing as NULL. provider.defaultSerializeValue(null, jgen); } private String printableRefStack() { StringBuilder sb = new StringBuilder(); Object[] objects = getObjectRefStack().toArray(); for (int i = objects.length - 1; i >= 0; i--) { sb.append(objects[i].getClass().getSimpleName()); if (i > 0) { sb.append("->"); } } return sb.toString(); } private Deque getObjectRefStack() { if (objectReferenceStackTL.get() == null) { objectReferenceStackTL.set(new ArrayDeque<>()); } return objectReferenceStackTL.get(); } private JsonSerializer wrapIfNeeded(JsonSerializer serializer) { return serializer instanceof BeanSerializerBase ? new CircularLoopHandlingSerializer<>((BeanSerializer) serializer, failOnCircularReferences) : serializer; } }