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

com.proofpoint.event.client.EventFieldMetadata Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010 Proofpoint, 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.proofpoint.event.client;

import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Map;

import static com.proofpoint.event.client.EventDataType.validateFieldValueType;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNullElse;

class EventFieldMetadata
{
    public static final Comparator NAME_COMPARATOR = comparing(a -> a.name);

    public enum ContainerType
    {
        ITERABLE, MAP, MULTIMAP;

        @Override
        public String toString()
        {
            return name().toLowerCase();
        }
    }

    private final String name;
    private final Method method;
    private final EventDataType eventDataType;
    private final EventTypeMetadata nestedType;
    private final ContainerType containerType;

    EventFieldMetadata(String name, Method method, EventDataType eventDataType, EventTypeMetadata nestedType, ContainerType containerType)
    {
        Preconditions.checkArgument((eventDataType != null) || (nestedType != null), "both eventDataType and nestedType are null");
        Preconditions.checkArgument((eventDataType == null) || (nestedType == null), "both eventDataType and nestedType are set");

        this.name = name;
        this.method = method;
        this.eventDataType = eventDataType;
        this.nestedType = nestedType;
        this.containerType = containerType;
    }

    private Object getValue(Object event)
            throws InvalidEventException
    {
        try {
            return method.invoke(event);
        }
        catch (Exception e) {
            throw new InvalidEventException(requireNonNullElse(e.getCause(), e),
                    "Unable to get value of event field %s: Exception occurred while invoking [%s]", name, method.toGenericString());
        }
    }

    public void writeField(JsonGenerator jsonGenerator, Object event)
            throws IOException
    {
        writeField(jsonGenerator, event, new ArrayDeque<>());
    }

    private void writeField(JsonGenerator jsonGenerator, Object event, Deque objectStack)
            throws IOException
    {
        Object value = getValue(event);
        if (value != null) {
            jsonGenerator.writeFieldName(name);
            if (containerType == ContainerType.ITERABLE) {
                validateFieldValueType(value, Iterable.class);
                writeArray(jsonGenerator, (Iterable) value, objectStack);
            }
            else if (containerType == ContainerType.MAP) {
                validateFieldValueType(value, Map.class);
                writeMap(jsonGenerator, (Map) value, objectStack);
            }
            else if (containerType == ContainerType.MULTIMAP) {
                validateFieldValueType(value, Multimap.class);
                writeMultimap(jsonGenerator, (Multimap) value, objectStack);
            }
            else {
                writeFieldValue(jsonGenerator, value, objectStack);
            }
        }
    }

    private void writeFieldValue(JsonGenerator jsonGenerator, Object value, Deque objectStack)
            throws IOException
    {
        if (eventDataType != null) {
            eventDataType.writeFieldValue(jsonGenerator, value);
        }
        else {
            validateFieldValueType(value, nestedType.getEventClass());
            writeObject(jsonGenerator, value, objectStack);
        }
    }

    private void writeArray(JsonGenerator jsonGenerator, Iterable value, Deque objectStack)
            throws IOException
    {
        jsonGenerator.writeStartArray();
        for (Object item : value) {
            writeFieldValue(jsonGenerator, item, objectStack);
        }
        jsonGenerator.writeEndArray();
    }

    private void writeMap(JsonGenerator jsonGenerator, Map value, Deque objectStack)
            throws IOException
    {
        jsonGenerator.writeStartObject();
        for (Map.Entry entry : value.entrySet()) {
            jsonGenerator.writeFieldName((String) entry.getKey());
            writeFieldValue(jsonGenerator, entry.getValue(), objectStack);
        }
        jsonGenerator.writeEndObject();
    }

    private void writeMultimap(JsonGenerator jsonGenerator, Multimap value, Deque objectStack)
            throws IOException
    {
        jsonGenerator.writeStartObject();
        for (Map.Entry> entry : value.asMap().entrySet()) {
            jsonGenerator.writeFieldName((String) entry.getKey());
            writeArray(jsonGenerator, entry.getValue(), objectStack);
        }
        jsonGenerator.writeEndObject();
    }

    private void writeObject(JsonGenerator jsonGenerator, Object value, Deque objectStack)
            throws IOException
    {
        checkForCycles(value, objectStack);
        objectStack.push(value);
        jsonGenerator.writeStartObject();
        for (EventFieldMetadata field : nestedType.getFields()) {
            field.writeField(jsonGenerator, value, objectStack);
        }
        jsonGenerator.writeEndObject();
        objectStack.pop();
    }

    private static void checkForCycles(Object value, Deque objectStack)
            throws InvalidEventException
    {
        for (Object o : objectStack) {
            if (value == o) {
                List path = Lists.reverse(Lists.newArrayList(objectStack));
                throw new InvalidEventException("Cycle detected in event data: %s", path);
            }
        }
    }
}