io.vertigo.vega.rest.engine.GoogleJsonEngine.java Source code

Java tutorial

Introduction

Here is the source code for io.vertigo.vega.rest.engine.GoogleJsonEngine.java

Source

/**
 * vertigo - simple java starter
 *
 * Copyright (C) 2013, 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.rest.engine;

import io.vertigo.core.spaces.component.ComponentInfo;
import io.vertigo.core.spaces.definiton.DefinitionReference;
import io.vertigo.dynamo.collections.model.Facet;
import io.vertigo.dynamo.collections.model.FacetValue;
import io.vertigo.dynamo.collections.model.FacetedQueryResult;
import io.vertigo.dynamo.domain.metamodel.DtDefinition;
import io.vertigo.dynamo.domain.metamodel.DtField;
import io.vertigo.dynamo.domain.model.DtList;
import io.vertigo.dynamo.domain.model.DtObject;
import io.vertigo.dynamo.domain.util.DtObjectUtil;
import io.vertigo.lang.JsonExclude;
import io.vertigo.lang.Option;
import io.vertigo.util.StringUtil;
import io.vertigo.vega.rest.EndPointTypeUtil;
import io.vertigo.vega.rest.model.DtListDelta;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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;

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

    /** {@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)));//TODO +stack;
    }

    /** {@inheritDoc} */
    @Override
    public <D extends Object> 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> UiObjectExtended<D> uiObjectExtendedFromJson(final String json, final Type paramType) {
       final Type typeOfDest = createParameterizedType(UiObjectExtended.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> UiList<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(UiList.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 (EndPointTypeUtil.isAssignableFrom(DtObject.class, paramType)) {
                    final Type typeOfDest = createParameterizedType(UiObject.class, paramType);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else if (EndPointTypeUtil.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 = createParameterizedType(UiListDelta.class, dtoClass);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else if (EndPointTypeUtil.isAssignableFrom(DtList.class, paramType)) {
                    final Class<DtObject> dtoClass = (Class<DtObject>) ((ParameterizedType) paramType)
                            .getActualTypeArguments()[0]; //we known that DtListDelta has one parameterized type
                    final Type typeOfDest = createParameterizedType(UiList.class, dtoClass);
                    value = gson.fromJson(jsonSubElement, typeOfDest);
                } else {
                    value = (Serializable) 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 ParameterizedType() {

            @Override
            public Type[] getActualTypeArguments() {
                return typeArguments;
            }

            @Override
            public Type getOwnerType() {
                return null;
            }

            @Override
            public Type getRawType() {
                return rawClass;
            }
        };
    }

    private static class UiObjectDeserializer<D extends DtObject> implements JsonDeserializer<UiObject<D>> {
        @Override
        public UiObject<D> deserialize(final JsonElement json, final Type typeOfT,
                final JsonDeserializationContext context) {
            final Type[] typeParameters = ((ParameterizedType) typeOfT).getActualTypeArguments();
            final Class<D> dtoClass = (Class<D>) typeParameters[0]; // Id has only one parameterized type T
            final JsonObject jsonObject = json.getAsJsonObject();
            final D inputDto = context.deserialize(jsonObject, dtoClass);
            final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition(dtoClass);
            final Set<String> dtFields = getFieldNames(dtDefinition);
            final Set<String> modifiedFields = new HashSet<>();
            for (final Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                final String fieldName = entry.getKey();
                if (dtFields.contains(fieldName)) { //we only keep fields of this dtObject
                    modifiedFields.add(fieldName);
                }
            }
            //Send a alert if no fields match the DtObject ones : details may be a security issue ?
            if (modifiedFields.isEmpty()) {
                final Set<String> jsonEntry = new HashSet<>();
                for (final Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                    jsonEntry.add(entry.getKey());
                }
                throw new JsonSyntaxException("Received Json's fields doesn't match " + dtoClass.getSimpleName()
                        + " ones : " + jsonEntry);
            }
            final UiObject<D> uiObject = new UiObject<>(inputDto, modifiedFields);
            if (jsonObject.has(SERVER_SIDE_TOKEN_FIELDNAME)) {
                uiObject.setServerSideToken(jsonObject.get(SERVER_SIDE_TOKEN_FIELDNAME).getAsString());
            }
            return uiObject;
        }
    }

    /*private static class UiObjectExtendedDeserializer<D extends DtObject> implements JsonDeserializer<UiObjectExtended<D>> {
       @Override
       public UiObjectExtended<D> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
     final Type[] typeParameters = ((ParameterizedType) typeOfT).getActualTypeArguments();
     final Class<D> dtoClass = (Class<D>) typeParameters[0]; // Id has only one parameterized type T
     final Type uiObjectType = createParameterizedType(UiObject.class, dtoClass);
     final UiObject<D> uiObject = context.deserialize(json, uiObjectType);
     final Set<String> uiObjectModifiedFields = uiObject.getModifiedFields();
     final UiObjectExtended<D> uiObjectExtended = new UiObjectExtended(uiObject);
        
     final JsonObject jsonObject = json.getAsJsonObject();
     for (final Entry<String, JsonElement> entry : jsonObject.entrySet()) {
        final String key = entry.getKey();
        final JsonElement jsonSubElement = entry.getValue();
        if (!uiObjectModifiedFields.contains(key)) {
           //Can't type value : bad solution.
           uiObjectExtended.put(key, jsonSubElement.getAsString());
        }
     }
     return uiObjectExtended;
       }
    }*/

    private static Set<String> getFieldNames(final DtDefinition dtDefinition) {
        final Set<String> dtFieldNames = new HashSet<>();
        for (final DtField dtField : dtDefinition.getFields()) {
            dtFieldNames.add(StringUtil.constToLowerCamelCase(dtField.getName()));
        }
        return dtFieldNames;
    }

    private static class UiListDeltaDeserializer<D extends DtObject> implements JsonDeserializer<UiListDelta<D>> {
        @Override
        public UiListDelta<D> deserialize(final JsonElement json, final Type typeOfT,
                final JsonDeserializationContext context) {
            final Type[] typeParameters = ((ParameterizedType) typeOfT).getActualTypeArguments();
            final Class<D> dtoClass = (Class<D>) typeParameters[0]; // Id has only one parameterized type T
            final Type uiObjectType = createParameterizedType(UiObject.class, dtoClass);
            final JsonObject jsonObject = json.getAsJsonObject();

            final Map<String, UiObject<D>> collCreates = parseUiObjectMap(jsonObject, "collCreates", uiObjectType,
                    context);
            final Map<String, UiObject<D>> collUpdates = parseUiObjectMap(jsonObject, "collUpdates", uiObjectType,
                    context);
            final Map<String, UiObject<D>> collDeletes = parseUiObjectMap(jsonObject, "collDeletes", uiObjectType,
                    context);

            final UiListDelta<D> uiListDelta = new UiListDelta<>(dtoClass, collCreates, collUpdates, collDeletes);
            return uiListDelta;
        }

        private Map<String, UiObject<D>> parseUiObjectMap(final JsonObject jsonObject, final String propertyName,
                final Type uiObjectType, final JsonDeserializationContext context) {
            final Map<String, UiObject<D>> uiObjectMap = new HashMap<>();
            final JsonObject jsonUiObjectMap = jsonObject.getAsJsonObject(propertyName);
            if (jsonUiObjectMap != null) {
                for (final Entry<String, JsonElement> entry : jsonUiObjectMap.entrySet()) {
                    final String entryName = entry.getKey();
                    final UiObject<D> inputDto = context.deserialize(entry.getValue(), uiObjectType);
                    uiObjectMap.put(entryName, inputDto);
                }
            }
            return uiObjectMap;
        }
    }

    private static class UiListDeserializer<D extends DtObject> implements JsonDeserializer<UiList<D>> {
        @Override
        public UiList<D> deserialize(final JsonElement json, final Type typeOfT,
                final JsonDeserializationContext context) throws JsonParseException {
            final Type[] typeParameters = ((ParameterizedType) typeOfT).getActualTypeArguments();
            final Class<D> dtoClass = (Class<D>) typeParameters[0]; // Id has only one parameterized type T
            final Type uiObjectType = createParameterizedType(UiObject.class, dtoClass);
            final JsonArray jsonArray = json.getAsJsonArray();

            final UiList<D> uiList = new UiList<>(dtoClass);
            for (final JsonElement element : jsonArray) {
                final UiObject<D> inputDto = context.deserialize(element, uiObjectType);
                uiList.add(inputDto);
            }
            return uiList;
        }
    }

    private Gson createGson() {
        return new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").setPrettyPrinting()
                //TODO  registerTypeAdapter(String.class, new EmptyStringAsNull<>())// add "" <=> null
                //.serializeNulls()//On veut voir les null
                .registerTypeAdapter(UiObject.class, new UiObjectDeserializer<>())
                .registerTypeAdapter(UiListDelta.class, new UiListDeltaDeserializer<>())
                .registerTypeAdapter(UiList.class, new UiListDeserializer<>())
                //.registerTypeAdapter(UiObjectExtended.class, new UiObjectExtendedDeserializer<>())
                /*.registerTypeAdapter(DtObjectExtended.class, new JsonSerializer<DtObjectExtended<?>>() {
                   @Override
                   public JsonElement serialize(final DtObjectExtended<?> src, final Type typeOfSrc, final JsonSerializationContext context) {
                      final JsonObject jsonObject = new JsonObject();
                      final JsonObject jsonInnerObject = (JsonObject) context.serialize(src.getInnerObject());
                      for (final Entry<String, JsonElement> entry : jsonInnerObject.entrySet()) {
                         jsonObject.add(entry.getKey(), entry.getValue());
                      }
                      for (final Entry<String, Serializable> entry : src.entrySet()) {
                         jsonObject.add(entry.getKey(), context.serialize(entry.getValue()));
                      }
                      return jsonObject;
                   }
                })*/
                .registerTypeAdapter(FacetedQueryResult.class, new JsonSerializer<FacetedQueryResult>() {
                    @Override
                    public JsonElement serialize(final FacetedQueryResult facetedQueryResult, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        final JsonObject jsonObject = new JsonObject();
                        //1- add result list as data
                        final JsonArray jsonData = (JsonArray) context.serialize(facetedQueryResult.getDtList());
                        jsonObject.add("data", jsonData);

                        //2- add facet list as facets
                        final List<Facet> facets = facetedQueryResult.getFacets();
                        final JsonArray facetList = new JsonArray();
                        for (final Facet facet : facets) {
                            final JsonObject jsonFacet = new JsonObject();

                            final Map<String, Long> maps = new HashMap<>();
                            for (final Entry<FacetValue, Long> entry : facet.getFacetValues().entrySet()) {
                                maps.put(entry.getKey().getLabel().getDisplay(), entry.getValue());
                            }

                            final JsonObject jsonFacetValues = (JsonObject) context.serialize(maps);
                            final String facetName = facet.getDefinition().getLabel().getDisplay();
                            jsonFacet.add(facetName, jsonFacetValues);
                            facetList.add(jsonFacet);
                        }
                        jsonObject.add("facets", context.serialize(facetList));
                        return jsonObject;
                    }
                }).registerTypeAdapter(ComponentInfo.class, new JsonSerializer<ComponentInfo>() {
                    @Override
                    public JsonElement serialize(final ComponentInfo componentInfo, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        final JsonObject jsonObject = new JsonObject();
                        jsonObject.add(componentInfo.getTitle(), context.serialize(componentInfo.getValue()));
                        return jsonObject;
                    }
                }).registerTypeAdapter(List.class, new JsonSerializer<List>() {

                    @Override
                    public JsonElement serialize(final List src, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        if (src.isEmpty()) {
                            return null;
                        }
                        return context.serialize(src);
                    }
                }).registerTypeAdapter(Map.class, new JsonSerializer<Map>() {

                    @Override
                    public JsonElement serialize(final Map src, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        if (src.isEmpty()) {
                            return null;
                        }
                        return context.serialize(src);
                    }
                }).registerTypeAdapter(DefinitionReference.class, new JsonSerializer<DefinitionReference>() {

                    @Override
                    public JsonElement serialize(final DefinitionReference src, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        return context.serialize(src.get().getName());
                    }
                }).registerTypeAdapter(Option.class, new JsonSerializer<Option>() {

                    @Override
                    public JsonElement serialize(final Option src, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        if (src.isDefined()) {
                            return context.serialize(src.get());
                        }
                        return null; //rien
                    }
                }).registerTypeAdapter(Class.class, new JsonSerializer<Class>() {

                    @Override
                    public JsonElement serialize(final Class src, final Type typeOfSrc,
                            final JsonSerializationContext context) {
                        return new JsonPrimitive(src.getName());
                    }
                }).addSerializationExclusionStrategy(new ExclusionStrategy() {
                    @Override
                    public boolean shouldSkipField(final FieldAttributes arg0) {
                        if (arg0.getAnnotation(JsonExclude.class) != null) {
                            return true;
                        }
                        return false;
                    }

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