io.vertigo.vega.engines.webservice.json.GoogleJsonEngine.java Source code

Java tutorial

Introduction

Here is the source code for io.vertigo.vega.engines.webservice.json.GoogleJsonEngine.java

Source

/**
 * vertigo - simple java starter
 *
 * Copyright (C) 2013-2017, KleeGroup, direction.technique@kleegroup.com (http://www.kleegroup.com)
 * KleeGroup, Centre d'affaire la Boursidiere - BP 159 - 92357 Le Plessis Robinson Cedex - France
 *
 * 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.vertigo.vega.engines.webservice.json;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.inject.Inject;
import javax.inject.Named;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import io.vertigo.core.definition.DefinitionReference;
import io.vertigo.dynamo.collections.model.FacetedQueryResult;
import io.vertigo.dynamo.domain.metamodel.DtDefinition;
import io.vertigo.dynamo.domain.metamodel.DtField.FieldType;
import io.vertigo.dynamo.domain.model.DtList;
import io.vertigo.dynamo.domain.model.DtListState;
import io.vertigo.dynamo.domain.model.DtObject;
import io.vertigo.dynamo.domain.model.URI;
import io.vertigo.dynamo.domain.model.VAccessor;
import io.vertigo.dynamo.domain.util.DtObjectUtil;
import io.vertigo.lang.JsonExclude;
import io.vertigo.lang.Tuples;
import io.vertigo.lang.WrappedException;
import io.vertigo.util.ClassUtil;
import io.vertigo.util.StringUtil;
import io.vertigo.vega.webservice.WebServiceTypeUtil;
import io.vertigo.vega.webservice.model.DtListDelta;
import io.vertigo.vega.webservice.model.UiObject;

/**
 * @author pchretien, npiedeloup
 */
public final class GoogleJsonEngine implements JsonEngine {
    private final Gson gson;

    private enum SearchApiVersion {
        V1(FacetedQueryResultJsonSerializerV1.class), //first api
        V2(FacetedQueryResultJsonSerializerV2.class), //with array instead of object
        V3(FacetedQueryResultJsonSerializerV3.class), //with code label, count on facets
        V4(FacetedQueryResultJsonSerializerV4.class); //with highlights and code, label for facet

        private final Class<? extends JsonSerializer<FacetedQueryResult<?, ?>>> jsonSerializerClass;

        <C extends JsonSerializer<FacetedQueryResult<?, ?>>> SearchApiVersion(final Class<C> jsonSerializerClass) {
            this.jsonSerializerClass = jsonSerializerClass;
        }

        Class<? extends JsonSerializer<FacetedQueryResult<?, ?>>> getJsonSerializerClass() {
            return jsonSerializerClass;
        }
    }

    @Inject
    public GoogleJsonEngine(@Named("searchApiVersion") final Optional<String> searchApiVersionStr) {
        final SearchApiVersion searchApiVersion = SearchApiVersion
                .valueOf(searchApiVersionStr.orElse(SearchApiVersion.V4.name()));
        gson = createGson(searchApiVersion);
    }

    /** {@inheritDoc} */
    @Override
    public String toJson(final Object data) {
        return gson.toJson(data);
    }

    /** {@inheritDoc} */
    @Override
    public String toJsonWithMeta(final Object data, final Map<String, Serializable> metaDatas,
            final Set<String> includedFields, final Set<String> excludedFields) {
        final JsonElement jsonValue = gson.toJsonTree(data);
        filterFields(jsonValue, includedFields, excludedFields);

        if (metaDatas.isEmpty() && data instanceof List) {
            return gson.toJson(jsonValue); //only case where result wasn't an object
        }

        final JsonObject jsonResult;
        if (data instanceof List) {
            jsonResult = new JsonObject();
            jsonResult.add(LIST_VALUE_FIELDNAME, jsonValue);
        } else {
            jsonResult = jsonValue.getAsJsonObject();
        }
        final JsonObject jsonMetaData = gson.toJsonTree(metaDatas).getAsJsonObject();
        for (final Entry<String, JsonElement> entry : jsonMetaData.entrySet()) {
            jsonResult.add(entry.getKey(), entry.getValue());
        }
        return gson.toJson(jsonResult);
    }

    private void filterFields(final JsonElement jsonElement, final Set<String> includedFields,
            final Set<String> excludedFields) {
        if (jsonElement.isJsonArray()) {
            final JsonArray jsonArray = jsonElement.getAsJsonArray();
            for (final JsonElement jsonSubElement : jsonArray) {
                filterFields(jsonSubElement, includedFields, excludedFields);
            }
        } else if (jsonElement.isJsonObject()) {
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            for (final String excludedField : excludedFields) {
                jsonObject.remove(excludedField);
            }
            if (!includedFields.isEmpty()) {
                final Set<String> notIncludedFields = new HashSet<>();
                for (final Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                    if (!includedFields.contains(entry.getKey())) {
                        notIncludedFields.add(entry.getKey());
                    }
                }
                for (final String notIncludedField : notIncludedFields) {
                    jsonObject.remove(notIncludedField);
                }
            }

        }
        //else Primitive : no exclude
    }

    /** {@inheritDoc} */
    @Override
    public String toJsonError(final Throwable th) {
        final String exceptionMessage = th.getMessage() != null ? th.getMessage() : th.getClass().getSimpleName();
        return gson.toJson(Collections.singletonMap("globalErrors", Collections.singletonList(exceptionMessage)));
    }

    /** {@inheritDoc} */
    @Override
    public <D> D fromJson(final String json, final Type paramType) {
        return gson.fromJson(json, paramType);
    }

    /** {@inheritDoc} */
    @Override
    public <D extends DtObject> UiObject<D> uiObjectFromJson(final String json, final Type paramType) {
        final Type typeOfDest = createParameterizedType(UiObject.class, paramType);
        return gson.fromJson(json, typeOfDest);
    }

    /** {@inheritDoc} */
    @Override
    public <D extends DtObject> UiListDelta<D> uiListDeltaFromJson(final String json, final Type paramType) {
        final Class<DtObject> dtoClass = (Class<DtObject>) ((ParameterizedType) paramType)
                .getActualTypeArguments()[0]; //we known that DtListDelta has one parameterized type
        final Type typeOfDest = createParameterizedType(UiListDelta.class, dtoClass);
        return gson.fromJson(json, typeOfDest);
    }

    /** {@inheritDoc} */
    @Override
    public <D extends DtObject> UiListModifiable<D> uiListFromJson(final String json, final Type paramType) {
        final Class<DtObject> dtoClass = (Class<DtObject>) ((ParameterizedType) paramType)
                .getActualTypeArguments()[0]; //we known that DtList has one parameterized type
        final Type typeOfDest = createParameterizedType(UiListModifiable.class, dtoClass);
        return gson.fromJson(json, typeOfDest);
    }

    /** {@inheritDoc} */
    @Override
    public UiContext uiContextFromJson(final String json, final Map<String, Type> paramTypes) {
        final UiContext result = new UiContext();
        try {
            final JsonElement jsonElement = new JsonParser().parse(json);
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            for (final Entry<String, Type> entry : paramTypes.entrySet()) {
                final String key = entry.getKey();
                final Type paramType = entry.getValue();
                final JsonElement jsonSubElement = jsonObject.get(key);

                final Serializable value;
                if (WebServiceTypeUtil.isAssignableFrom(DtObject.class, paramType)) {
                    final Type typeOfDest = new KnownParameterizedType(UiObject.class, paramType);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else if (WebServiceTypeUtil.isAssignableFrom(DtListDelta.class, paramType)) {
                    final Class<DtObject> dtoClass = (Class<DtObject>) ((ParameterizedType) paramType)
                            .getActualTypeArguments()[0]; //we known that DtListDelta has one parameterized type
                    final Type typeOfDest = new KnownParameterizedType(UiListDelta.class, dtoClass);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else if (WebServiceTypeUtil.isAssignableFrom(DtList.class, paramType)) {
                    final Class<DtObject> dtoClass = (Class<DtObject>) ((ParameterizedType) paramType)
                            .getActualTypeArguments()[0]; //we known that DtList has one parameterized type
                    final Type typeOfDest = new KnownParameterizedType(UiListModifiable.class, dtoClass);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else {
                    value = gson.fromJson(jsonSubElement, paramType);
                }
                result.put(key, value);
            }
            return result;
        } catch (final IllegalStateException e) {
            throw new JsonSyntaxException("JsonObject expected", e);
        }
    }

    private static Type createParameterizedType(final Class<?> rawClass, final Type paramType) {
        final Type[] typeArguments = { paramType };
        return new KnownParameterizedType(rawClass, typeArguments);
    }

    private static final class JsonExclusionStrategy implements ExclusionStrategy {
        /** {@inheritDoc} */
        @Override
        public boolean shouldSkipField(final FieldAttributes arg0) {
            return arg0.getAnnotation(JsonExclude.class) != null;
        }

        @Override
        public boolean shouldSkipClass(final Class<?> arg0) {
            return false;
        }
    }

    private static final class ClassJsonSerializer implements JsonSerializer<Class> {
        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final Class src, final Type typeOfSrc,
                final JsonSerializationContext context) {
            return new JsonPrimitive(src.getName());
        }
    }

    private static final class OptionJsonSerializer implements JsonSerializer<Optional> {
        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final Optional src, final Type typeOfSrc,
                final JsonSerializationContext context) {
            if (src.isPresent()) {
                return context.serialize(src.get());
            }
            return null; //rien
        }
    }

    private final class DtObjectJsonAdapter<D extends DtObject> implements JsonSerializer<D>, JsonDeserializer<D> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final D src, final Type typeOfSrc, final JsonSerializationContext context) {
            final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition(src.getClass());
            final JsonObject jsonObject = new JsonObject();

            dtDefinition.getFields().stream().forEach(field -> {
                jsonObject.add(StringUtil.constToLowerCamelCase(field.getName()),
                        context.serialize(field.getDataAccessor().getValue(src)));
            });

            Stream.of(src.getClass().getDeclaredFields())
                    .filter(field -> VAccessor.class.isAssignableFrom(field.getType()))
                    .map(field -> getAccessor(field, src)).filter(VAccessor::isLoaded).forEach(accessor -> {
                        jsonObject.add(accessor.getRole(), context.serialize(accessor.get()));
                    });
            return jsonObject;

        }

        @Override
        public D deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
                throws JsonParseException {
            final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition((Class<D>) typeOfT);

            // we use as base the default deserialization
            final D dtObject = (D) gson.getDelegateAdapter(null, TypeToken.get(typeOfT)).fromJsonTree(json);
            final JsonObject jsonObject = json.getAsJsonObject();

            // case of the lazy objet passed
            Stream.of(((Class<D>) typeOfT).getDeclaredFields())
                    .filter(field -> VAccessor.class.isAssignableFrom(field.getType()))
                    .map(field -> Tuples.of(field, getAccessor(field, dtObject)))
                    .filter(tuple -> jsonObject.has(tuple.getVal2().getRole()))
                    .forEach(tuple -> tuple.getVal2().set(context.deserialize(
                            jsonObject.get(tuple.getVal2().getRole()), ClassUtil.getGeneric(tuple.getVal1()))));

            // case of the fk we need to handle after because it's the primary information
            dtDefinition.getFields().stream().filter(field -> field.getType() == FieldType.FOREIGN_KEY)
                    .forEach(field -> field.getDataAccessor().setValue(dtObject,
                            context.deserialize(jsonObject.get(StringUtil.constToLowerCamelCase(field.getName())),
                                    field.getDomain().getDataType().getJavaClass())));

            return dtObject;

        }

    }

    private static VAccessor getAccessor(final Field field, final Object object) {
        try {
            field.setAccessible(true);
            return (VAccessor) field.get(object);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw WrappedException.wrap(e);
        }
    }

    private static final class VAccessorJsonSerializer implements JsonSerializer<VAccessor> {
        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final VAccessor src, final Type typeOfSrc,
                final JsonSerializationContext context) {
            return null;

        }

    }

    private static final class DefinitionReferenceJsonSerializer implements JsonSerializer<DefinitionReference> {
        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final DefinitionReference src, final Type typeOfSrc,
                final JsonSerializationContext context) {
            return context.serialize(src.get().getName());
        }
    }

    private static final class MapJsonSerializer implements JsonSerializer<Map> {
        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final Map src, final Type typeOfSrc, final JsonSerializationContext context) {
            if (src.isEmpty()) {
                return null;
            }
            return context.serialize(src);
        }
    }

    private static final class ListJsonSerializer implements JsonSerializer<List> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final List src, final Type typeOfSrc, final JsonSerializationContext context) {
            if (src.isEmpty()) {
                return null;
            }
            return context.serialize(src);
        }
    }

    private static final class URIJsonAdapter implements JsonSerializer<URI>, JsonDeserializer<URI> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final URI uri, final Type typeOfSrc, final JsonSerializationContext context) {
            return new JsonPrimitive(uri.urn());
        }

        /** {@inheritDoc} */
        @Override
        public URI deserialize(final JsonElement json, final Type paramType,
                final JsonDeserializationContext paramJsonDeserializationContext) {
            return URI.fromURN(json.getAsString());
        }
    }

    private static class UTCDateAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final Date date, final Type type,
                final JsonSerializationContext jsonSerializationContext) {
            //Use INPUT_DATE_FORMATS[0] => ISO8601 format
            return new JsonPrimitive(UTCDateUtil.format(date));
        }

        /** {@inheritDoc} */
        @Override
        public Date deserialize(final JsonElement jsonElement, final Type type,
                final JsonDeserializationContext jsonDeserializationContext) {
            return UTCDateUtil.parse(jsonElement.getAsString());
        }
    }

    private static class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final LocalDate date, final Type typeOfSrc,
                final JsonSerializationContext context) {
            return new JsonPrimitive(date.format(DateTimeFormatter.ISO_LOCAL_DATE)); // "yyyy-mm-dd"
        }

        /** {@inheritDoc} */
        @Override
        public LocalDate deserialize(final JsonElement jsonElement, final Type type,
                final JsonDeserializationContext jsonDeserializationContext) {
            return LocalDate.parse(jsonElement.getAsString(), DateTimeFormatter.ISO_LOCAL_DATE);
        }
    }

    private static class ZonedDateTimeAdapter
            implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {

        /** {@inheritDoc} */
        @Override
        public JsonElement serialize(final ZonedDateTime date, final Type typeOfSrc,
                final JsonSerializationContext context) {
            return new JsonPrimitive(
                    date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("UTC")))); // "yyyy-mm-ddTHH:MI:SSZ"
        }

        /** {@inheritDoc} */
        @Override
        public ZonedDateTime deserialize(final JsonElement jsonElement, final Type type,
                final JsonDeserializationContext jsonDeserializationContext) {
            return ZonedDateTime.parse(jsonElement.getAsString(),
                    DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("UTC")));
        }
    }

    private static class EmptyStringAsNull implements JsonDeserializer<String> {

        /** {@inheritDoc} */
        @Override
        public String deserialize(final JsonElement jsonElement, final Type type,
                final JsonDeserializationContext jsonDeserializationContext) {
            final String value = jsonElement.getAsString();
            if (value != null && value.isEmpty()) {
                return null;
            }
            return value;
        }
    }

    private Gson createGson(final SearchApiVersion searchApiVersion) {
        try {
            return new GsonBuilder().setPrettyPrinting()
                    //.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
                    .registerTypeHierarchyAdapter(DtObject.class, new DtObjectJsonAdapter())
                    .registerTypeAdapter(Date.class, new UTCDateAdapter())
                    .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
                    .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter())
                    .registerTypeAdapter(String.class, new EmptyStringAsNull())// add "" <=> null
                    .registerTypeAdapter(UiObject.class, new UiObjectDeserializer<>())
                    .registerTypeAdapter(UiListDelta.class, new UiListDeltaDeserializer<>())
                    .registerTypeAdapter(UiListModifiable.class, new UiListDeserializer<>())
                    .registerTypeAdapter(DtList.class, new DtListDeserializer<>())
                    .registerTypeAdapter(DtListState.class, new DtListStateDeserializer())
                    .registerTypeAdapter(FacetedQueryResult.class,
                            searchApiVersion.getJsonSerializerClass().newInstance())
                    .registerTypeAdapter(List.class, new ListJsonSerializer())
                    .registerTypeAdapter(Map.class, new MapJsonSerializer())
                    .registerTypeAdapter(DefinitionReference.class, new DefinitionReferenceJsonSerializer())
                    .registerTypeAdapter(Optional.class, new OptionJsonSerializer())
                    .registerTypeAdapter(VAccessor.class, new VAccessorJsonSerializer())
                    .registerTypeAdapter(Class.class, new ClassJsonSerializer())
                    .registerTypeAdapter(URI.class, new URIJsonAdapter())
                    .addSerializationExclusionStrategy(new JsonExclusionStrategy()).create();
        } catch (InstantiationException | IllegalAccessException e) {
            throw WrappedException.wrap(e, "Can't create Gson");
        }
    }
}