io.vertigo.vega.plugins.rest.handler.JsonConverterRestHandlerPlugin.java Source code

Java tutorial

Introduction

Here is the source code for io.vertigo.vega.plugins.rest.handler.JsonConverterRestHandlerPlugin.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.plugins.rest.handler;

import io.vertigo.dynamo.domain.model.DtList;
import io.vertigo.dynamo.domain.model.DtObject;
import io.vertigo.lang.Assertion;
import io.vertigo.lang.Option;
import io.vertigo.vega.impl.rest.RestHandlerPlugin;
import io.vertigo.vega.rest.engine.JsonEngine;
import io.vertigo.vega.rest.engine.UiContext;
import io.vertigo.vega.rest.engine.UiList;
import io.vertigo.vega.rest.engine.UiListDelta;
import io.vertigo.vega.rest.engine.UiObject;
import io.vertigo.vega.rest.exception.SessionException;
import io.vertigo.vega.rest.exception.VSecurityException;
import io.vertigo.vega.rest.metamodel.EndPointDefinition;
import io.vertigo.vega.rest.metamodel.EndPointParam;
import io.vertigo.vega.rest.metamodel.EndPointParam.ImplicitParam;
import io.vertigo.vega.rest.metamodel.EndPointParam.RestParamType;
import io.vertigo.vega.rest.model.DtListDelta;
import io.vertigo.vega.rest.model.DtObjectExtended;
import io.vertigo.vega.rest.model.UiListState;
import io.vertigo.vega.token.TokenManager;

import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;

import spark.QueryParamsMap;
import spark.Request;
import spark.Response;

import com.google.gson.JsonSyntaxException;

/**
 * Params handler.
 * It's an handler barrier : bellow this handler anything is object, over this handler it's json.
 * Extract and Json convert.
 * @author npiedeloup
 */
public final class JsonConverterRestHandlerPlugin implements RestHandlerPlugin {
    private static final String SERVER_SIDE_MANDATORY = "ServerSideToken mandatory";
    private static final String FORBIDDEN_OPERATION_FIELD_MODIFICATION = "Can't modify field:";

    private final JsonEngine jsonWriterEngine;
    private final JsonEngine jsonReaderEngine;

    private final TokenManager tokenManager;

    /**
     * encodeType.
     */
    enum EncoderType {
        /** Type JSON simple */
        JSON(""),
        /** Type JSON UiContext */
        JSON_UI_CONTEXT("json+uicontext"),
        /** Type JSON list */
        JSON_LIST("json+list:%s"),
        /** Type JSON list with meta */
        JSON_LIST_META("json+list:%s+meta"),
        /** Type JSON entity */
        JSON_ENTITY("json+entity:%s"),
        /** Type JSON entity + meta */
        JSON_ENTITY_META("json+entity:%s+meta");

        private final Pattern contentTypePattern;
        private final String contentType;

        private EncoderType(final String contentType) {
            this.contentType = contentType;
            contentTypePattern = Pattern.compile(contentType.replaceAll("%s", ".+"));
        }

        /**
         * @param entityName Entity name
         * @return contentType
         */
        public String createContentType(final String entityName) {
            return String.format(contentType, entityName);
        }

        /**
         * @param testedContentType contentType to test
         * @return If testedContentType is 'this' EncoderType
         */
        public boolean isContentType(final String testedContentType) {
            return contentTypePattern.matcher(testedContentType).find();
        }
    }

    /**
     * EncodedType : encoderType + entityName.
     */
    static class EncodedType {
        private final EncoderType encoderType;
        private final String contentType;

        /**
         * constructor.
         * @param encoderType encoderType
         * @param entityName entityName
         */
        EncodedType(final EncoderType encoderType, final String entityName) {
            this.encoderType = encoderType;
            contentType = encoderType.createContentType(entityName);
        }

        /**
         * @return encoderType
         */
        public EncoderType getEncoderType() {
            return encoderType;
        }

        /**
         * @return contentType
         */
        public String getContentType() {
            return contentType;
        }
    }

    /**
     * @param tokenManager tokenManager
     * @param jsonWriterEngine jsonWriterEngine
     * @param jsonReaderEngine jsonReaderEngine
     */
    @Inject
    public JsonConverterRestHandlerPlugin(final TokenManager tokenManager, final JsonEngine jsonWriterEngine,
            final JsonEngine jsonReaderEngine) {
        Assertion.checkNotNull(tokenManager);
        Assertion.checkNotNull(jsonWriterEngine);
        Assertion.checkNotNull(jsonReaderEngine);
        //-----
        this.tokenManager = tokenManager;
        this.jsonWriterEngine = jsonWriterEngine;
        this.jsonReaderEngine = jsonReaderEngine;
    }

    /** {@inheritDoc} */
    @Override
    public boolean accept(final EndPointDefinition endPointDefinition) {
        return true;
    }

    /** {@inheritDoc}  */
    @Override
    public Object handle(final Request request, final Response response, final RouteContext routeContext,
            final HandlerChain chain) throws VSecurityException, SessionException {
        //we can't read body at first : because if it's a multipart request call body() disabled getParts() access.
        UiContext innerBodyParsed = null;
        for (final EndPointParam endPointParam : routeContext.getEndPointDefinition().getEndPointParams()) {
            try {
                final Object value;
                if (VFileUtil.isVFileParam(endPointParam)) {
                    value = VFileUtil.readVFileParam(request, endPointParam);
                } else {
                    switch (endPointParam.getParamType()) {
                    case Body:
                        value = readValue(request.body(), endPointParam);
                        break;
                    case InnerBody:
                        if (innerBodyParsed == null) {
                            //we read all InnerBody when we get the first one
                            innerBodyParsed = readInnerBodyValue(request.body(),
                                    routeContext.getEndPointDefinition().getEndPointParams());
                        }
                        value = innerBodyParsed.get(endPointParam.getName());
                        break;
                    case Path:
                        value = readPrimitiveValue(request.params(endPointParam.getName()),
                                endPointParam.getType());
                        break;
                    case Query:
                        value = readQueryValue(request.queryMap(), endPointParam);
                        break;
                    case Header:
                        value = readPrimitiveValue(request.headers(endPointParam.getName()),
                                endPointParam.getType());
                        break;
                    case Implicit:
                        value = readImplicitValue(endPointParam, request, response, routeContext);
                        break;
                    default:
                        throw new IllegalArgumentException("RestParamType : " + endPointParam.getFullName());
                    }
                }
                Assertion.checkNotNull(value, "RestParam not found : {0}", endPointParam);
                routeContext.setParamValue(endPointParam, value);
            } catch (final JsonSyntaxException e) {
                throw new JsonSyntaxException("Error parsing param " + endPointParam.getFullName() + " on service "
                        + routeContext.getEndPointDefinition().getVerb() + " "
                        + routeContext.getEndPointDefinition().getPath(), e);
            }
        }

        final Object result = chain.handle(request, response, routeContext);
        return convertResult(result, request, response, routeContext);
    }

    private String convertResult(final Object result, final Request request, final Response response,
            final RouteContext routeContext) {
        if (result == null) {
            response.status(HttpServletResponse.SC_NO_CONTENT);
            return ""; //jetty understand null as 404 not found
        } else if (VFileUtil.isVFileResult(result)) {
            VFileUtil.sendVFile(result, request, response);
            return ""; // response already send but can't send null : javaspark understand it as : not consumed here
        } else if (result instanceof HttpServletResponse) {
            Assertion.checkState(((HttpServletResponse) result).isCommitted(),
                    "The httpResponse returned wasn't close. Ensure you have close your streams.");
            //-----
            return ""; // response already send but can't send null : javaspark understand it as : not consumed here
        } else if (result instanceof String) {
            final String resultString = (String) result;
            final int length = resultString.length();
            Assertion.checkArgument(
                    !(resultString.charAt(0) == '{' && resultString.charAt(length - 1) == '}')
                            && !(resultString.charAt(0) == '[' && resultString.charAt(length - 1) == ']'),
                    "Can't return pre-build json : {0}", resultString);
            response.type("text/plain;charset=UTF-8");
            return (String) result;
        } else {
            final EncodedType encodedType = findEncodedType(result);
            final StringBuilder contentType = new StringBuilder("application/json;charset=UTF-8");
            if (encodedType.getEncoderType() != EncoderType.JSON) {
                contentType.append(";").append(encodedType.getContentType());
            }
            response.type(contentType.toString());
            return writeValue(result, response, encodedType, routeContext.getEndPointDefinition());
        }
    }

    private EncodedType findEncodedType(final Object result) {
        final EncodedType encodedType;
        if (result instanceof List) {
            if (result instanceof DtList) {
                final DtList<?> dtList = (DtList<?>) result;
                if (hasComplexTypeMeta(dtList)) {
                    encodedType = new EncodedType(EncoderType.JSON_LIST_META,
                            dtList.getDefinition().getClassSimpleName());
                } else {
                    encodedType = new EncodedType(EncoderType.JSON_LIST,
                            dtList.getDefinition().getClassSimpleName());
                }
            } else {
                encodedType = new EncodedType(EncoderType.JSON_LIST, Object.class.getSimpleName());
            }
        } else if (result instanceof DtObject) {
            encodedType = new EncodedType(EncoderType.JSON_ENTITY, result.getClass().getSimpleName());
        } else if (result instanceof DtObjectExtended<?>) {
            encodedType = new EncodedType(EncoderType.JSON_ENTITY_META,
                    ((DtObjectExtended<?>) result).getInnerObject().getClass().getSimpleName());
        } else if (result instanceof UiContext) {
            encodedType = new EncodedType(EncoderType.JSON_UI_CONTEXT, result.getClass().getSimpleName());
        } else {
            encodedType = new EncodedType(EncoderType.JSON, result.getClass().getSimpleName());
        }
        return encodedType;

    }

    private boolean hasComplexTypeMeta(final DtList<?> dtList) {
        for (final String entry : dtList.getMetaDataNames()) {
            final Option<Serializable> value = dtList.getMetaData(entry, Serializable.class);
            if (value.isDefined()) {
                final Class<?> metaClass = value.get().getClass();
                if (!(metaClass.isPrimitive() || String.class.isAssignableFrom(metaClass)
                        || Integer.class.isAssignableFrom(metaClass) || Long.class.isAssignableFrom(metaClass)
                        || Float.class.isAssignableFrom(metaClass) || Double.class.isAssignableFrom(metaClass)
                        || Date.class.isAssignableFrom(metaClass))) {
                    return true;
                }
            }
        }
        return false;
    }

    private UiContext readInnerBodyValue(final String jsonBody, final List<EndPointParam> endPointParams)
            throws VSecurityException {
        final List<EndPointParam> innerBodyEndPointParams = new ArrayList<>();
        final Map<String, Type> innerBodyParams = new HashMap<>();
        for (final EndPointParam endPointParam : endPointParams) {
            if (endPointParam.getParamType() == RestParamType.InnerBody
                    || endPointParam.getParamType() == RestParamType.Implicit) {
                innerBodyEndPointParams.add(endPointParam);
                innerBodyParams.put(endPointParam.getName(), endPointParam.getGenericType());
            }
        }
        if (!innerBodyParams.isEmpty()) {
            final UiContext uiContext = jsonReaderEngine.uiContextFromJson(jsonBody, innerBodyParams);
            for (final EndPointParam endPointParam : innerBodyEndPointParams) {
                final Serializable value = uiContext.get(endPointParam.getName());
                if (value instanceof UiObject) {
                    postReadUiObject((UiObject<DtObject>) value, endPointParam.getName(), endPointParam,
                            tokenManager);
                } else if (value instanceof UiListDelta) {
                    postReadUiListDelta((UiListDelta<DtObject>) value, endPointParam.getName(), endPointParam,
                            tokenManager);
                }
            }
            return uiContext;
        }
        return null;
    }

    private <D> D readPrimitiveValue(final String json, final Class<D> paramClass) {
        if (json == null) {
            return null;
        } else if (paramClass.isPrimitive()) {
            return jsonReaderEngine.fromJson(json, paramClass);
        } else if (String.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(json);
        } else if (Integer.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(Integer.valueOf(json));
        } else if (Long.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(Long.valueOf(json));
        } else if (Float.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(Float.valueOf(json));
        } else if (Double.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(Double.valueOf(json));
        } else if (Date.class.isAssignableFrom(paramClass)) {
            return paramClass.cast(jsonReaderEngine.fromJson(json, paramClass));
        } else {
            throw new IllegalArgumentException("Unsupported type " + paramClass.getSimpleName());
        }
    }

    private <D> D readQueryValue(final QueryParamsMap queryMap, final EndPointParam endPointParam)
            throws VSecurityException {
        final Class<D> paramClass = (Class<D>) endPointParam.getType();
        final String paramName = endPointParam.getName();
        if (queryMap == null) {
            return null;
        }
        if (UiListState.class.isAssignableFrom(paramClass) || DtObject.class.isAssignableFrom(paramClass)) {
            return (D) readValue(convertToJson(queryMap, endPointParam.getName()), endPointParam);
        }
        return readPrimitiveValue(queryMap.get(paramName).value(), paramClass);
    }

    private Object readImplicitValue(final EndPointParam endPointParam, final Request request,
            final Response response, final RouteContext routeContext) {
        switch (ImplicitParam.valueOf(endPointParam.getName())) {
        case UiMessageStack:
            return routeContext.getUiMessageStack();
        case Request:
            return request.raw();
        case Response:
            return response.raw();
        default:
            throw new IllegalArgumentException("ImplicitParam : " + endPointParam.getName());
        }
    }

    private String convertToJson(final QueryParamsMap queryMap, final String queryPrefix) {
        final String checkedQueryPrefix = queryPrefix.isEmpty() ? "" : queryPrefix + ".";
        final Map<String, Object> queryParams = new HashMap<>();
        for (final Entry<String, String[]> entry : queryMap.toMap().entrySet()) {
            if (entry.getKey().startsWith(checkedQueryPrefix)) {
                final String[] value = entry.getValue();
                final Object simplerValue = value.length == 0 ? null : value.length == 1 ? value[0] : value;
                queryParams.put(entry.getKey().substring(checkedQueryPrefix.length()), simplerValue);
            }
        }
        return jsonWriterEngine.toJson(queryParams);
    }

    private Object readValue(final String json, final EndPointParam endPointParam) throws VSecurityException {
        final Class<?> paramClass = endPointParam.getType();
        final Type paramGenericType = endPointParam.getGenericType();
        if (json == null) {
            return null;
        } else if (String.class.isAssignableFrom(paramClass)) {
            return json;
        } else if (Integer.class.isAssignableFrom(paramClass)) {
            return Integer.valueOf(json);
        } else if (Long.class.isAssignableFrom(paramClass)) {
            return Long.valueOf(json);
        } else if (DtObject.class.isAssignableFrom(paramClass)) {
            final UiObject<DtObject> uiObject = jsonReaderEngine.<DtObject>uiObjectFromJson(json, paramGenericType);
            if (uiObject != null) {
                postReadUiObject(uiObject, "", endPointParam, tokenManager);
            }
            return uiObject;
        } else if (DtListDelta.class.isAssignableFrom(paramClass)) {
            final UiListDelta<DtObject> uiListDelta = jsonReaderEngine.<DtObject>uiListDeltaFromJson(json,
                    paramGenericType);
            if (uiListDelta != null) {
                postReadUiListDelta(uiListDelta, "", endPointParam, tokenManager);
            }
            return uiListDelta;
        } else if (DtList.class.isAssignableFrom(paramClass)) {
            final UiList<DtObject> uiList = jsonReaderEngine.<DtObject>uiListFromJson(json, paramGenericType);
            if (uiList != null) {
                postReadUiList(uiList, "", endPointParam, tokenManager);
            }
            return uiList;
        } else if (DtObjectExtended.class.isAssignableFrom(paramClass)) {
            throw new IllegalArgumentException(
                    "Unsupported type DtObjectExtended (use multiple params instead, /*implicit body*/ myDto, @InnerBodyParams others...).");
        } else if (UiContext.class.isAssignableFrom(paramClass)) {
            throw new IllegalArgumentException("Unsupported type UiContext (use @InnerBodyParams instead).");
        } else {
            return jsonReaderEngine.fromJson(json, paramClass);
        }
    }

    private static void postReadUiObject(final UiObject<DtObject> uiObject, final String inputKey,
            final EndPointParam endPointParam, final TokenManager uiSecurityTokenManager)
            throws VSecurityException {
        uiObject.setInputKey(inputKey);
        checkUnauthorizedFieldModifications(uiObject, endPointParam);

        if (endPointParam.isNeedServerSideToken()) {
            final String accessToken = uiObject.getServerSideToken();
            if (accessToken == null) {
                throw new VSecurityException(SERVER_SIDE_MANDATORY); //same message for no ServerSideToken or bad ServerSideToken
            }
            final Option<Serializable> serverSideObject;
            if (endPointParam.isConsumeServerSideToken()) {
                //if exception : token is consume. It's for security reason : no replay on bad request (brute force password)
                serverSideObject = uiSecurityTokenManager.getAndRemove(accessToken);
            } else {
                serverSideObject = uiSecurityTokenManager.get(accessToken);
            }
            if (serverSideObject.isEmpty()) {
                throw new VSecurityException(SERVER_SIDE_MANDATORY); //same message for no ServerSideToken or bad ServerSideToken
            }
            uiObject.setServerSideObject((DtObject) serverSideObject.get());
        }
    }

    private static void postReadUiListDelta(final UiListDelta<DtObject> uiListDelta, final String inputKey,
            final EndPointParam endPointParam, final TokenManager uiSecurityTokenManager)
            throws VSecurityException {
        final String prefix = inputKey.length() > 0 ? inputKey + "." : "";
        for (final Map.Entry<String, UiObject<DtObject>> entry : uiListDelta.getCreatesMap().entrySet()) {
            final String uiObjectInputKey = prefix + entry.getKey();
            postReadUiObject(entry.getValue(), uiObjectInputKey, endPointParam, uiSecurityTokenManager);
        }
        for (final Map.Entry<String, UiObject<DtObject>> entry : uiListDelta.getUpdatesMap().entrySet()) {
            final String uiObjectInputKey = prefix + entry.getKey();
            postReadUiObject(entry.getValue(), uiObjectInputKey, endPointParam, uiSecurityTokenManager);
        }
        for (final Map.Entry<String, UiObject<DtObject>> entry : uiListDelta.getDeletesMap().entrySet()) {
            final String uiObjectInputKey = prefix + entry.getKey();
            postReadUiObject(entry.getValue(), uiObjectInputKey, endPointParam, uiSecurityTokenManager);
        }
    }

    private static void postReadUiList(final UiList<DtObject> uiList, final String inputKey,
            final EndPointParam endPointParam, final TokenManager uiSecurityTokenManager)
            throws VSecurityException {
        final String prefix = inputKey.length() > 0 ? inputKey + "." : "";
        int index = 0;
        for (final UiObject<DtObject> entry : uiList) {
            final String uiObjectInputKey = prefix + "idx" + index;
            postReadUiObject(entry, uiObjectInputKey, endPointParam, uiSecurityTokenManager);
            index++;
        }
    }

    private static void checkUnauthorizedFieldModifications(final UiObject<DtObject> uiObject,
            final EndPointParam endPointParam) throws VSecurityException {
        for (final String excludedField : endPointParam.getExcludedFields()) {
            if (uiObject.isModified(excludedField)) {
                throw new VSecurityException(FORBIDDEN_OPERATION_FIELD_MODIFICATION + excludedField);
            }
        }
        final Set<String> includedFields = endPointParam.getIncludedFields();
        if (!includedFields.isEmpty()) {
            for (final String modifiedField : uiObject.getModifiedFields()) {
                if (!includedFields.contains(modifiedField)) {
                    throw new VSecurityException(FORBIDDEN_OPERATION_FIELD_MODIFICATION + modifiedField);
                }
            }
        }
    }

    private String writeValue(final Object value, final Response response, final EncodedType encodedType,
            final EndPointDefinition endPointDefinition) {
        Assertion.checkNotNull(value);
        //-----
        final String tokenId;
        if (endPointDefinition.isServerSideSave()) {
            Assertion.checkArgument(
                    DtObject.class.isInstance(value) || DtObjectExtended.class.isInstance(value)
                            || DtList.class.isInstance(value) || UiContext.class.isInstance(value),
                    "Return type can't be ServerSide : {0}", value.getClass().getSimpleName());
            tokenId = tokenManager.put((Serializable) value);
        } else {
            tokenId = null;
        }

        switch (encodedType.getEncoderType()) {
        case JSON:
            return jsonWriterEngine.toJson(value);
        case JSON_ENTITY:
            return toJson(value, Collections.<String, Serializable>emptyMap(), tokenId,
                    endPointDefinition.getIncludedFields(), endPointDefinition.getExcludedFields());
        case JSON_ENTITY_META:
            final DtObjectExtended<?> dtoExtended = (DtObjectExtended<?>) value;
            return toJson(dtoExtended.getInnerObject(), dtoExtended, tokenId,
                    endPointDefinition.getIncludedFields(), endPointDefinition.getExcludedFields());
        case JSON_LIST:
            writeListMetaToHeader((List) value, response);
            return toJson(value, Collections.<String, Serializable>emptyMap(), tokenId,
                    endPointDefinition.getIncludedFields(), endPointDefinition.getExcludedFields());
        case JSON_LIST_META:
            return toJson(value, getListMetas((DtList) value), tokenId, endPointDefinition.getIncludedFields(),
                    endPointDefinition.getExcludedFields());
        case JSON_UI_CONTEXT:
            //TODO build json in jsonWriterEngine
            final StringBuilder sb = new StringBuilder().append("{");
            String sep = "";
            for (final Map.Entry<String, Serializable> entry : ((UiContext) value).entrySet()) {
                sb.append(sep);
                final Serializable entryValue = entry.getValue();
                String encodedValue;
                if (entryValue instanceof DtList) {
                    final DtList<?> dtList = (DtList<?>) entryValue;
                    encodedValue = writeValue(entryValue, response, new EncodedType(EncoderType.JSON_LIST_META,
                            dtList.getDefinition().getClassSimpleName()), endPointDefinition);
                } else if (entryValue instanceof DtObject || entryValue instanceof DtObjectExtended) {
                    encodedValue = writeValue(entryValue, response, findEncodedType(entryValue),
                            endPointDefinition);
                } else {
                    encodedValue = jsonWriterEngine.toJson(entryValue);
                }
                sb.append("\"").append(entry.getKey()).append("\":").append(encodedValue).append("");
                sep = ", ";
            }
            sb.append("}");
            return sb.toString();
        default:
            throw new IllegalArgumentException(
                    "Return type :" + value.getClass().getSimpleName() + " is not supported");
        }
    }

    private void writeListMetaToHeader(final List<?> list, final Response response) {
        if (list instanceof DtList) {
            final DtList<?> dtList = (DtList<?>) list;
            for (final String entry : dtList.getMetaDataNames()) {
                final Option<Serializable> value = dtList.getMetaData(entry, Serializable.class);
                if (value.isDefined()) {
                    if (value.get() instanceof String) {
                        response.header(entry, (String) value.get()); //TODO escape somethings ?
                    } else {
                        response.header(entry, jsonWriterEngine.toJson(value.get()));
                    }
                }
            }
        } //else nothing, there is no meta on standard list
    }

    private Map<String, Serializable> getListMetas(final DtList<?> dtList) {
        final Map<String, Serializable> metaDatas = new HashMap<>();
        for (final String entry : dtList.getMetaDataNames()) {
            final Option<Serializable> value = dtList.getMetaData(entry, Serializable.class);
            if (value.isDefined()) {
                metaDatas.put(entry, value.get());
            }
        }
        return metaDatas;
    }

    private String toJson(final Object value, final Map<String, Serializable> metaData, final String tokenId,
            final Set<String> includedFields, final Set<String> excludedFields) {
        final Map<String, Serializable> metaDataToSend;
        if (tokenId != null) {
            metaDataToSend = new HashMap<>(metaData);
            metaDataToSend.put(JsonEngine.SERVER_SIDE_TOKEN_FIELDNAME, tokenId);
        } else {
            metaDataToSend = metaData;
        }
        return jsonWriterEngine.toJsonWithMeta(value, metaDataToSend, includedFields, excludedFields);
    }
}