Java tutorial
/* * Copyright 2016 Google Inc. All Rights Reserved. * * 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.google.api.server.spi; import com.google.api.server.spi.config.annotationreader.ApiAnnotationIntrospector; import com.google.api.server.spi.config.model.ApiSerializationConfig; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.std.MapSerializer; import com.fasterxml.jackson.databind.type.ArrayType; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.MapType; import java.io.IOException; import java.lang.reflect.Array; import java.util.Collection; import java.util.Map; /** * Utilities for {@link ObjectMapper}. */ public class ObjectMapperUtil { /** * Creates an Endpoints standard object mapper that allows unquoted field names and unknown * properties. * * Note on unknown properties: When Apiary FE supports a strict mode where properties * are checked against the schema, BE can just ignore unknown properties. This way, FE does * not need to filter out everything that the BE doesn't understand. Before that's done, * a property name with a typo in it, for example, will just be ignored by the BE. */ public static ObjectMapper createStandardObjectMapper() { return createStandardObjectMapper(null); } /** * Creates an Endpoints standard object mapper that allows unquoted field names and unknown * properties. * * Note on unknown properties: When Apiary FE supports a strict mode where properties * are checked against the schema, BE can just ignore unknown properties. This way, FE does * not need to filter out everything that the BE doesn't understand. Before that's done, * a property name with a typo in it, for example, will just be ignored by the BE. */ public static ObjectMapper createStandardObjectMapper(ApiSerializationConfig config) { ObjectMapper objectMapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true) .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).setSerializerFactory( BeanSerializerFactory.instance.withSerializerModifier(new DeepEmptyCheckingModifier())); AnnotationIntrospector pair = AnnotationIntrospector.pair(new ApiAnnotationIntrospector(config), new JacksonAnnotationIntrospector()); objectMapper.setAnnotationIntrospector(pair); return objectMapper; } /** * A {@link BeanSerializerModifier} which modifies output to omit deeply empty collections. * A collection is considered empty if it has zero elements, or all of its elements are also * deeply empty, recursively. */ private static class DeepEmptyCheckingModifier extends BeanSerializerModifier { @Override public JsonSerializer<?> modifyArraySerializer(SerializationConfig config, ArrayType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) { return new DeepEmptyCheckingSerializer<>(serializer); } @Override public JsonSerializer<?> modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) { return new DeepEmptyCheckingSerializer<>(serializer); } @Override public JsonSerializer<?> modifyMapSerializer(SerializationConfig config, MapType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) { if (serializer instanceof MapSerializer) { // For some reason, NON_EMPTY is not being propagated to MapSerializer, so we replace it // with one that has it set. return new DeepEmptyCheckingSerializer<>( ((MapSerializer) serializer).withContentInclusion(JsonInclude.Include.NON_EMPTY)); } return serializer; } } /** * A {@link JsonSerializer} whose {@link #isEmpty(SerializerProvider, Object)} method checks for * "deep" emptiness, rather than simply calling the container's empty method. In this case, a * container is considered empty if all of its values are null or are containers that are deeply * empty. */ private static class DeepEmptyCheckingSerializer<T> extends JsonSerializer<T> implements ContextualSerializer { private final JsonSerializer<T> delegate; DeepEmptyCheckingSerializer(JsonSerializer<T> delegate) { this.delegate = delegate; } @Override public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException { delegate.serialize(value, gen, serializers); } @Override public boolean isEmpty(SerializerProvider provider, Object value) { return ObjectMapperUtil.isEmpty(value); } @Override public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { if (delegate instanceof ContextualSerializer) { return new DeepEmptyCheckingSerializer<>( ((ContextualSerializer) delegate).createContextual(provider, property)); } return this; } } private static boolean isEmpty(Object value) { Class<?> clazz = value.getClass(); if (clazz.isArray()) { int len = Array.getLength(value); for (int i = 0; i < len; i++) { Object element = Array.get(value, i); if (element != null && !isEmpty(element)) { return false; } } return true; } else if (Collection.class.isAssignableFrom(clazz)) { Collection<?> c = (Collection<?>) value; for (Object element : c) { if (element != null && !isEmpty(element)) { return false; } } return true; } else if (Map.class.isAssignableFrom(clazz)) { Map<?, ?> m = (Map<?, ?>) value; for (Object entryValue : m.values()) { if (entryValue != null && !isEmpty(entryValue)) { return false; } } return true; } return false; } }