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

com.yahoo.document.json.DocumentUpdateJsonSerializer Maven / Gradle / Ivy

There is a newer version: 8.498.26
Show newest version
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document.json;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.yahoo.document.DataType;
import com.yahoo.document.Document;
import com.yahoo.document.DocumentUpdate;
import com.yahoo.document.FieldPath;
import com.yahoo.document.annotation.AnnotationReference;
import com.yahoo.document.datatypes.Array;
import com.yahoo.document.datatypes.BoolFieldValue;
import com.yahoo.document.datatypes.ByteFieldValue;
import com.yahoo.document.datatypes.CollectionFieldValue;
import com.yahoo.document.datatypes.DoubleFieldValue;
import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.FloatFieldValue;
import com.yahoo.document.datatypes.IntegerFieldValue;
import com.yahoo.document.datatypes.LongFieldValue;
import com.yahoo.document.datatypes.MapFieldValue;
import com.yahoo.document.datatypes.PredicateFieldValue;
import com.yahoo.document.datatypes.Raw;
import com.yahoo.document.datatypes.ReferenceFieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.Struct;
import com.yahoo.document.datatypes.StructuredFieldValue;
import com.yahoo.document.datatypes.TensorFieldValue;
import com.yahoo.document.datatypes.WeightedSet;
import com.yahoo.document.fieldpathupdate.AddFieldPathUpdate;
import com.yahoo.document.fieldpathupdate.AssignFieldPathUpdate;
import com.yahoo.document.fieldpathupdate.FieldPathUpdate;
import com.yahoo.document.fieldpathupdate.RemoveFieldPathUpdate;
import com.yahoo.document.json.readers.SingleValueReader;
import com.yahoo.document.serialization.DocumentUpdateWriter;
import com.yahoo.document.serialization.FieldWriter;
import com.yahoo.document.update.AddValueUpdate;
import com.yahoo.document.update.ArithmeticValueUpdate;
import com.yahoo.document.update.AssignValueUpdate;
import com.yahoo.document.update.ClearValueUpdate;
import com.yahoo.document.update.FieldUpdate;
import com.yahoo.document.update.MapValueUpdate;
import com.yahoo.document.update.RemoveValueUpdate;
import com.yahoo.document.update.TensorAddUpdate;
import com.yahoo.document.update.TensorModifyUpdate;
import com.yahoo.document.update.TensorRemoveUpdate;
import com.yahoo.document.update.ValueUpdate;
import com.yahoo.vespa.objects.FieldBase;
import com.yahoo.vespa.objects.Serializer;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import static com.yahoo.document.json.JsonSerializationHelper.*;

/**
 * The DocumentUpdateJsonSerializer utility class is used to serialize a DocumentUpdate instance using the JSON format described in
 * Document JSON Format: The Update Structure
 *
 * @see #serialize(com.yahoo.document.DocumentUpdate)
 * @author Vegard Sjonfjell
 */
public class DocumentUpdateJsonSerializer {

    private final JsonFactory jsonFactory = new JsonFactory();
    private final JsonDocumentUpdateWriter writer = new JsonDocumentUpdateWriter();
    private JsonGenerator generator;

    /**
     * Instantiate a DocumentUpdateJsonSerializer that outputs JSON to an OutputStream
     */
    public DocumentUpdateJsonSerializer(OutputStream outputStream) {
        wrapIOException(() -> generator = jsonFactory.createGenerator(outputStream));
    }

    /**
     * Instantiate a DocumentUpdateJsonSerializer that writes JSON using existing JsonGenerator
     */
    public DocumentUpdateJsonSerializer(JsonGenerator generator) {
        this.generator = generator;
    }

    /**
     * Serialize a DocumentUpdate tree to JSON
     */
    public void serialize(DocumentUpdate update) {
        writer.write(update);
    }

    private class JsonDocumentUpdateWriter implements DocumentUpdateWriter, FieldWriter {
        @Override
        public void write(DocumentUpdate update) {
            wrapIOException(() -> {
                generator.writeStartObject();
                generator.writeStringField("update", update.getId().toString());

                if (update.getCondition().isPresent()) {
                    generator.writeStringField("condition", update.getCondition().getSelection());
                }

                Optional createIfNotExistent = update.getOptionalCreateIfNonExistent();
                if (createIfNotExistent.isPresent() && createIfNotExistent.get()) {
                    generator.writeBooleanField("create", createIfNotExistent.get());
                }

                generator.writeObjectFieldStart("fields");
                for (FieldUpdate up : update.fieldUpdates()) {
                    up.serialize(this);
                }

                update.fieldPathUpdates().stream()
                        .collect(Collectors.groupingBy(FieldPathUpdate::getFieldPath))
                        .forEach((fieldPath, fieldPathUpdates) ->
                                wrapIOException(() -> write(fieldPath, fieldPathUpdates, generator)));
                generator.writeEndObject();

                generator.writeEndObject();
                generator.flush();
            });
        }

        private void write(FieldPath fieldPath, Collection fieldPathUpdates, JsonGenerator generator) throws IOException {
            generator.writeObjectFieldStart(fieldPath.toString());

            for (FieldPathUpdate update : fieldPathUpdates) {
                if (writeArithmeticFieldPathUpdate(update, generator)) continue;
                generator.writeFieldName(update.getUpdateType().name().toLowerCase());

                if (update instanceof AssignFieldPathUpdate) {
                    AssignFieldPathUpdate assignUp = (AssignFieldPathUpdate) update;
                    if (assignUp.getExpression() != null) {
                        throw new RuntimeException("Unable to parse expression: " + assignUp.getExpression());
                    } else {
                        assignUp.getNewValue().serialize(null, this);
                    }
                } else if (update instanceof AddFieldPathUpdate) {
                    ((AddFieldPathUpdate) update).getNewValues().serialize(null, this);
                } else if (update instanceof RemoveFieldPathUpdate) {
                    generator.writeNumber(0);
                } else {
                    throw new RuntimeException("Unsupported fieldpath operation: " + update.getClass().getName());
                }
            }
            generator.writeEndObject();
        }

        // Returns true if fieldpath update was an arithmetic operation after writing it to the generator
        private boolean writeArithmeticFieldPathUpdate(FieldPathUpdate fieldPathUpdate, JsonGenerator generator) throws IOException {
            if (! (fieldPathUpdate instanceof AssignFieldPathUpdate)) return false;
            String expression = ((AssignFieldPathUpdate) fieldPathUpdate).getExpression();
            if (expression == null) return false;

            Matcher matcher = SingleValueReader.matchArithmeticOperation(expression);
            if (matcher.find()) {
                String updateOperation = SingleValueReader.ARITHMETIC_SIGN_TO_UPDATE_OPERATION.get(matcher.group(1));
                double value = Double.valueOf(matcher.group(2));

                generator.writeNumberField(updateOperation, value);
                return true;
            }

            return false;
        }

        @Override
        public void write(FieldUpdate fieldUpdate) {
            wrapIOException(() -> {
                generator.writeObjectFieldStart(fieldUpdate.getField().getName());

                ArrayList removeValueUpdates = new ArrayList<>();
                ArrayList addValueUpdates = new ArrayList<>();

                final DataType dataType = fieldUpdate.getField().getDataType();
                for (ValueUpdate valueUpdate : fieldUpdate.getValueUpdates()) {
                    if (valueUpdate instanceof RemoveValueUpdate) {
                        removeValueUpdates.add(valueUpdate);
                    } else if (valueUpdate instanceof AddValueUpdate) {
                        addValueUpdates.add(valueUpdate);
                    } else {
                        valueUpdate.serialize(this, dataType);
                    }
                }

                writeAddOrRemoveValueUpdates("remove", removeValueUpdates, dataType);
                writeAddOrRemoveValueUpdates("add", addValueUpdates, dataType);

                generator.writeEndObject();
            });
        }

        private void writeAddOrRemoveValueUpdates(String arrayFieldName, ArrayList valueUpdates, DataType dataType) throws IOException {
            if (!valueUpdates.isEmpty()) {
                generator.writeArrayFieldStart(arrayFieldName);
                for (ValueUpdate valueUpdate : valueUpdates) {
                    valueUpdate.serialize(this, dataType);
                }
                generator.writeEndArray();
            }
        }

        @Override
        public void write(AddValueUpdate update, DataType superType) {
            update.getValue().serialize(this);
        }

        /* This is the 'match' operation */
        @Override
        public void write(MapValueUpdate update, DataType superType) {
            wrapIOException(() -> {
                generator.writeObjectFieldStart("match");
                generator.writeFieldName("element");
                update.getValue().serialize(null, this);
                update.getUpdate().serialize(this, superType);
                generator.writeEndObject();
            });
        }

        @Override
        public void write(ArithmeticValueUpdate update) {
            final ArithmeticValueUpdate.Operator operator = update.getOperator();
            final String operationKey;

            switch (operator) {
                case ADD:
                    operationKey = "increment";
                    break;
                case DIV:
                    operationKey = "divide";
                    break;
                case MUL:
                    operationKey = "multiply";
                    break;
                case SUB:
                    operationKey = "decrement";
                    break;
                default:
                    throw new RuntimeException(String.format("Unrecognized arithmetic operator '%s'", operator.name));
            }

            wrapIOException(() -> generator.writeFieldName(operationKey));
            update.getValue().serialize(this);
        }

        @Override
        public void write(AssignValueUpdate update, DataType superType) {
            wrapIOException(() -> generator.writeFieldName("assign"));
            update.getValue().serialize(null, this);
        }

        @Override
        public void write(RemoveValueUpdate update, DataType superType) {
            update.getValue().serialize(null, this);
        }

        @Override
        public void write(ClearValueUpdate clearValueUpdate, DataType superType) {
            wrapIOException(() -> generator.writeNullField("assign"));
        }

        @Override
        public void write(TensorModifyUpdate update) {
            wrapIOException(() -> {
                generator.writeObjectFieldStart("modify");
                generator.writeFieldName("operation");
                generator.writeString(update.getOperation().name);
                if (update.getValue().getTensor().isPresent()) {
                    serializeTensorCells(generator, update.getValue().getTensor().get());
                }
                generator.writeEndObject();
            });
        }

        @Override
        public void write(TensorAddUpdate update) {
            wrapIOException(() -> {
                generator.writeObjectFieldStart("add");
                if (update.getValue().getTensor().isPresent()) {
                    serializeTensorCells(generator, update.getValue().getTensor().get());
                }
                generator.writeEndObject();
            });
        }

        @Override
        public void write(TensorRemoveUpdate update) {
            wrapIOException(() -> {
                generator.writeObjectFieldStart("remove");
                if (update.getValue().getTensor().isPresent()) {
                    serializeTensorAddresses(generator, update.getValue().getTensor().get());
                }
                generator.writeEndObject();
            });
        }

        @Override
        public void write(FieldBase field, FieldValue value) {
            throw new JsonSerializationException(String.format("Serialization of field values of type %s is not supported", value.getClass().getName()));
        }

        @Override
        public void write(FieldBase field, Document value) {
            throw new JsonSerializationException("Serialization of 'Document fields' is not supported");
        }

        @Override
        public  void write(FieldBase field, Array array) {
            serializeArrayField(this, generator, field, array);
        }

        @Override
        public  void write(FieldBase field, MapFieldValue map) {
            serializeMapField(this, generator, field, map);
        }

        @Override
        public void write(FieldBase field, ByteFieldValue value) {
            serializeByteField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, BoolFieldValue value) {
            serializeBoolField(generator, field, value);
        }

        @Override
        public  void write(FieldBase field, CollectionFieldValue value) {
            serializeCollectionField(this, generator, field, value);
        }

        @Override
        public void write(FieldBase field, DoubleFieldValue value) {
            serializeDoubleField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, FloatFieldValue value) {
            serializeFloatField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, IntegerFieldValue value) {
            serializeIntField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, LongFieldValue value) {
            serializeLongField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, Raw value) {
            serializeRawField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, PredicateFieldValue value) {
            serializePredicateField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, StringFieldValue value) {
            serializeStringField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, TensorFieldValue value) {
            serializeTensorField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, ReferenceFieldValue value) {
            serializeReferenceField(generator, field, value);
        }

        @Override
        public void write(FieldBase field, Struct value) {
            serializeStructField(this, generator, field, value);
        }

        @Override
        public void write(FieldBase field, StructuredFieldValue value) {
            serializeStructuredField(this, generator, field, value);
        }

        @Override
        public  void write(FieldBase field, WeightedSet weightedSet) {
            serializeWeightedSet(generator, field, weightedSet);
        }

        @Override
        public void write(FieldBase field, AnnotationReference value) {
            // Serialization of annotations are not implemented
        }

        @Override
        public Serializer putByte(FieldBase field, byte value) {
            serializeByte(generator, field, value);
            return this;
        }

        @Override
        public Serializer putShort(FieldBase field, short value) {
            serializeShort(generator, field, value);
            return this;
        }

        @Override
        public Serializer putInt(FieldBase field, int value) {
            serializeInt(generator, field, value);
            return this;
        }

        @Override
        public Serializer putLong(FieldBase field, long value) {
            serializeLong(generator, field, value);
            return this;
        }

        @Override
        public Serializer putFloat(FieldBase field, float value) {
            serializeFloat(generator, field, value);
            return this;
        }

        @Override
        public Serializer putDouble(FieldBase field, double value) {
            serializeDouble(generator, field, value);
            return this;
        }

        @Override
        public Serializer put(FieldBase field, byte[] value) {
            serializeByteArray(generator, field, value);
            return this;
        }

        @Override
        public Serializer put(FieldBase field, ByteBuffer value) {
            serializeByteBuffer(generator, field, value);
            return this;
        }

        @Override
        public Serializer put(FieldBase field, String value) {
            serializeString(generator, field, value);
            return this;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy