/*
* 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 io.airlift.event.client;
import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.annotations.Beta;
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.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.MoreObjects.firstNonNull;
import static io.airlift.event.client.EventDataType.validateFieldValueType;
@Beta
public class EventFieldMetadata
{
public static enum ContainerType
{
ITERABLE, MAP, MULTIMAP;
@Override
public String toString()
{
return name().toLowerCase();
}
}
private final String name;
private final Method method;
private final Optional<EventDataType> eventDataType;
private final Optional<EventTypeMetadata<?>> nestedType;
private final Optional<ContainerType> containerType;
EventFieldMetadata(String name, Method method, Optional<EventDataType> eventDataType, Optional<EventTypeMetadata<?>> nestedType, Optional<ContainerType> containerType)
{
Preconditions.checkArgument(eventDataType.isPresent() || nestedType.isPresent(), "both eventDataType and nestedType are unset");
Preconditions.checkArgument(!eventDataType.isPresent() || !nestedType.isPresent(), "both eventDataType and nestedType are set");
this.name = name;
this.method = method;
this.eventDataType = eventDataType;
this.nestedType = nestedType;
this.containerType = containerType;
}
public String getName()
{
return name;
}
/**
* Returns Optional.empty() when this field does not contain a nested type.
*/
public Optional<EventTypeMetadata<?>> getNestedType()
{
return nestedType;
}
/**
* Returns Optional.empty() when this field is not a container type.
*/
public Optional<ContainerType> getContainerType()
{
return containerType;
}
private Object getValue(Object event)
throws InvalidEventException
{
try {
return method.invoke(event);
}
catch (Exception e) {
throw new InvalidEventException(firstNonNull(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<Object> objectStack)
throws IOException
{
Object value = getValue(event);
if (value != null) {
jsonGenerator.writeFieldName(name);
if (containerType.isPresent()) {
if (containerType.get() == ContainerType.ITERABLE) {
validateFieldValueType(value, Iterable.class);
writeArray(jsonGenerator, (Iterable<?>) value, objectStack);
}
else if (containerType.get() == ContainerType.MAP) {
validateFieldValueType(value, Map.class);
writeMap(jsonGenerator, (Map<?, ?>) value, objectStack);
}
else if (containerType.get() == ContainerType.MULTIMAP) {
validateFieldValueType(value, Multimap.class);
writeMultimap(jsonGenerator, (Multimap<?, ?>) value, objectStack);
}
return;
}
writeFieldValue(jsonGenerator, value, objectStack);
}
}
private void writeFieldValue(JsonGenerator jsonGenerator, Object value, Deque<Object> objectStack)
throws IOException
{
if (eventDataType.isPresent()) {
eventDataType.get().writeFieldValue(jsonGenerator, value);
}
else {
validateFieldValueType(value, nestedType.get().getEventClass());
writeObject(jsonGenerator, value, objectStack);
}
}
private void writeArray(JsonGenerator jsonGenerator, Iterable<?> value, Deque<Object> objectStack)
throws IOException
{
jsonGenerator.writeStartArray();
for (Object item : value) {
writeFieldValue(jsonGenerator, item, objectStack);
}
jsonGenerator.writeEndArray();
}
private void writeMap(JsonGenerator jsonGenerator, Map<?, ?> value, Deque<Object> 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<Object> objectStack)
throws IOException
{
jsonGenerator.writeStartObject();
for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) {
jsonGenerator.writeFieldName((String) entry.getKey());
writeArray(jsonGenerator, entry.getValue(), objectStack);
}
jsonGenerator.writeEndObject();
}
private void writeObject(JsonGenerator jsonGenerator, Object value, Deque<Object> objectStack)
throws IOException
{
checkForCycles(value, objectStack);
objectStack.push(value);
jsonGenerator.writeStartObject();
for (EventFieldMetadata field : nestedType.get().getFields()) {
field.writeField(jsonGenerator, value, objectStack);
}
jsonGenerator.writeEndObject();
objectStack.pop();
}
private static void checkForCycles(Object value, Deque<Object> objectStack)
throws InvalidEventException
{
for (Object o : objectStack) {
if (value == o) {
List<Object> path = Lists.reverse(new ArrayList<>(objectStack));
throw new InvalidEventException("Cycle detected in event data: %s", path);
}
}
}
}