org.apache.olingo.commons.core.serialization.JsonDeserializer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.olingo.commons.core.serialization.JsonDeserializer.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.olingo.commons.core.serialization;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.data.Annotatable;
import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntitySet;
import org.apache.olingo.commons.api.data.Linked;
import org.apache.olingo.commons.api.data.LinkedComplexValue;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.data.ResWrap;
import org.apache.olingo.commons.api.data.Valuable;
import org.apache.olingo.commons.api.data.ValueType;
import org.apache.olingo.commons.api.domain.ODataError;
import org.apache.olingo.commons.api.domain.ODataLinkType;
import org.apache.olingo.commons.api.domain.ODataPropertyType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
import org.apache.olingo.commons.api.edm.geo.Geospatial;
import org.apache.olingo.commons.api.serialization.ODataDeserializer;
import org.apache.olingo.commons.api.serialization.ODataDeserializerException;
import org.apache.olingo.commons.core.data.AnnotationImpl;
import org.apache.olingo.commons.core.data.EntitySetImpl;
import org.apache.olingo.commons.core.data.LinkImpl;
import org.apache.olingo.commons.core.data.LinkedComplexValueImpl;
import org.apache.olingo.commons.core.data.PropertyImpl;
import org.apache.olingo.commons.core.edm.EdmTypeInfo;
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JsonDeserializer implements ODataDeserializer {

    protected final Pattern CUSTOM_ANNOTATION = Pattern.compile("(.+)@(.+)\\.(.+)");

    protected final ODataServiceVersion version;

    protected final boolean serverMode;

    protected String jsonType;

    protected String jsonId;

    protected String jsonETag;

    protected String jsonReadLink;

    protected String jsonEditLink;

    protected String jsonMediaEditLink;

    protected String jsonMediaReadLink;

    protected String jsonMediaContentType;

    protected String jsonMediaETag;

    protected String jsonAssociationLink;

    protected String jsonNavigationLink;

    protected String jsonCount;

    protected String jsonNextLink;

    protected String jsonDeltaLink;

    protected String jsonError;

    private JsonGeoValueDeserializer geoDeserializer;

    private JsonParser parser;

    public JsonDeserializer(final ODataServiceVersion version, final boolean serverMode) {
        this.version = version;
        this.serverMode = serverMode;

        jsonType = version.getJsonName(ODataServiceVersion.JsonKey.TYPE);
        jsonId = version.getJsonName(ODataServiceVersion.JsonKey.ID);
        jsonETag = version.getJsonName(ODataServiceVersion.JsonKey.ETAG);
        jsonReadLink = version.getJsonName(ODataServiceVersion.JsonKey.READ_LINK);
        jsonEditLink = version.getJsonName(ODataServiceVersion.JsonKey.EDIT_LINK);
        jsonMediaReadLink = version.getJsonName(ODataServiceVersion.JsonKey.MEDIA_READ_LINK);
        jsonMediaEditLink = version.getJsonName(ODataServiceVersion.JsonKey.MEDIA_EDIT_LINK);
        jsonMediaContentType = version.getJsonName(ODataServiceVersion.JsonKey.MEDIA_CONTENT_TYPE);
        jsonMediaETag = version.getJsonName(ODataServiceVersion.JsonKey.MEDIA_ETAG);
        jsonAssociationLink = version.getJsonName(ODataServiceVersion.JsonKey.ASSOCIATION_LINK);
        jsonNavigationLink = version.getJsonName(ODataServiceVersion.JsonKey.NAVIGATION_LINK);
        jsonCount = version.getJsonName(ODataServiceVersion.JsonKey.COUNT);
        jsonNextLink = version.getJsonName(ODataServiceVersion.JsonKey.NEXT_LINK);
        jsonDeltaLink = version.getJsonName(ODataServiceVersion.JsonKey.DELTA_LINK);
        jsonError = version.getJsonName(ODataServiceVersion.JsonKey.ERROR);
    }

    private JsonGeoValueDeserializer getGeoDeserializer() {
        if (geoDeserializer == null) {
            geoDeserializer = new JsonGeoValueDeserializer(version);
        }
        return geoDeserializer;
    }

    protected String getJSONAnnotation(final String string) {
        return StringUtils.prependIfMissing(string, "@");
    }

    protected String getTitle(final Map.Entry<String, JsonNode> entry) {
        return entry.getKey().substring(0, entry.getKey().indexOf('@'));
    }

    protected String setInline(final String name, final String suffix, final JsonNode tree, final ObjectCodec codec,
            final LinkImpl link) throws IOException {

        final String entityNamePrefix = name.substring(0, name.indexOf(suffix));
        if (tree.has(entityNamePrefix)) {
            final JsonNode inline = tree.path(entityNamePrefix);
            JsonEntityDeserializer entityDeserializer = new JsonEntityDeserializer(version, serverMode);

            if (inline instanceof ObjectNode) {
                link.setType(ODataLinkType.ENTITY_NAVIGATION.toString());
                link.setInlineEntity(entityDeserializer.doDeserialize(inline.traverse(codec)).getPayload());

            } else if (inline instanceof ArrayNode) {
                link.setType(ODataLinkType.ENTITY_SET_NAVIGATION.toString());

                final EntitySet entitySet = new EntitySetImpl();
                for (final Iterator<JsonNode> entries = inline.elements(); entries.hasNext();) {
                    entitySet.getEntities()
                            .add(entityDeserializer.doDeserialize(entries.next().traverse(codec)).getPayload());
                }

                link.setInlineEntitySet(entitySet);
            }
        }
        return entityNamePrefix;
    }

    protected void links(final Map.Entry<String, JsonNode> field, final Linked linked, final Set<String> toRemove,
            final JsonNode tree, final ObjectCodec codec) throws IOException {
        if (serverMode) {
            serverLinks(field, linked, toRemove, tree, codec);
        } else {
            clientLinks(field, linked, toRemove, tree, codec);
        }
    }

    private void clientLinks(final Map.Entry<String, JsonNode> field, final Linked linked,
            final Set<String> toRemove, final JsonNode tree, final ObjectCodec codec) throws IOException {

        if (field.getKey().endsWith(jsonNavigationLink)) {
            final LinkImpl link = new LinkImpl();
            link.setTitle(getTitle(field));
            link.setRel(
                    version.getNamespace(ODataServiceVersion.NamespaceKey.NAVIGATION_LINK_REL) + getTitle(field));

            if (field.getValue().isValueNode()) {
                link.setHref(field.getValue().textValue());
                link.setType(ODataLinkType.ENTITY_NAVIGATION.toString());
            }

            linked.getNavigationLinks().add(link);

            toRemove.add(field.getKey());
            toRemove.add(setInline(field.getKey(), jsonNavigationLink, tree, codec, link));
        } else if (field.getKey().endsWith(jsonAssociationLink)) {
            final LinkImpl link = new LinkImpl();
            link.setTitle(getTitle(field));
            link.setRel(
                    version.getNamespace(ODataServiceVersion.NamespaceKey.ASSOCIATION_LINK_REL) + getTitle(field));
            link.setHref(field.getValue().textValue());
            link.setType(ODataLinkType.ASSOCIATION.toString());
            linked.getAssociationLinks().add(link);

            toRemove.add(field.getKey());
        }
    }

    private void serverLinks(final Map.Entry<String, JsonNode> field, final Linked linked,
            final Set<String> toRemove, final JsonNode tree, final ObjectCodec codec) throws IOException {

        if (field.getKey().endsWith(Constants.JSON_BIND_LINK_SUFFIX)
                || field.getKey().endsWith(jsonNavigationLink)) {

            if (field.getValue().isValueNode()) {
                final String suffix = field.getKey().replaceAll("^.*@", "@");

                final LinkImpl link = new LinkImpl();
                link.setTitle(getTitle(field));
                link.setRel(version.getNamespace(ODataServiceVersion.NamespaceKey.NAVIGATION_LINK_REL)
                        + getTitle(field));
                link.setHref(field.getValue().textValue());
                link.setType(ODataLinkType.ENTITY_NAVIGATION.toString());
                linked.getNavigationLinks().add(link);

                toRemove.add(setInline(field.getKey(), suffix, tree, codec, link));
            } else if (field.getValue().isArray()) {
                for (final Iterator<JsonNode> itor = field.getValue().elements(); itor.hasNext();) {
                    final JsonNode node = itor.next();

                    final LinkImpl link = new LinkImpl();
                    link.setTitle(getTitle(field));
                    link.setRel(version.getNamespace(ODataServiceVersion.NamespaceKey.NAVIGATION_LINK_REL)
                            + getTitle(field));
                    link.setHref(node.asText());
                    link.setType(ODataLinkType.ENTITY_SET_NAVIGATION.toString());
                    linked.getNavigationLinks().add(link);
                    toRemove.add(setInline(field.getKey(), Constants.JSON_BIND_LINK_SUFFIX, tree, codec, link));
                }
            }
            toRemove.add(field.getKey());
        }
    }

    private Map.Entry<ODataPropertyType, EdmTypeInfo> guessPropertyType(final JsonNode node) {
        ODataPropertyType type;
        EdmTypeInfo typeInfo = null;

        if (node.isValueNode() || node.isNull()) {
            type = ODataPropertyType.PRIMITIVE;

            EdmPrimitiveTypeKind kind = EdmPrimitiveTypeKind.String;
            if (node.isShort()) {
                kind = EdmPrimitiveTypeKind.Int16;
            } else if (node.isInt()) {
                kind = EdmPrimitiveTypeKind.Int32;
            } else if (node.isLong()) {
                kind = EdmPrimitiveTypeKind.Int64;
            } else if (node.isBoolean()) {
                kind = EdmPrimitiveTypeKind.Boolean;
            } else if (node.isFloat()) {
                kind = EdmPrimitiveTypeKind.Single;
            } else if (node.isDouble()) {
                kind = EdmPrimitiveTypeKind.Double;
            } else if (node.isBigDecimal()) {
                kind = EdmPrimitiveTypeKind.Decimal;
            }
            typeInfo = new EdmTypeInfo.Builder().setTypeExpression(kind.getFullQualifiedName().toString()).build();
        } else if (node.isArray()) {
            type = ODataPropertyType.COLLECTION;
        } else if (node.isObject()) {
            if (node.has(Constants.ATTR_TYPE)) {
                type = ODataPropertyType.PRIMITIVE;
                typeInfo = new EdmTypeInfo.Builder()
                        .setTypeExpression("Edm.Geography" + node.get(Constants.ATTR_TYPE).asText()).build();
            } else {
                type = ODataPropertyType.COMPLEX;
            }
        } else {
            type = ODataPropertyType.EMPTY;
        }

        return new SimpleEntry<ODataPropertyType, EdmTypeInfo>(type, typeInfo);
    }

    protected void populate(final Annotatable annotatable, final List<Property> properties, final ObjectNode tree,
            final ObjectCodec codec) throws IOException, EdmPrimitiveTypeException {

        String type = null;
        Annotation annotation = null;
        for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
            final Map.Entry<String, JsonNode> field = itor.next();
            final Matcher customAnnotation = CUSTOM_ANNOTATION.matcher(field.getKey());

            if (field.getKey().charAt(0) == '@') {
                final Annotation entityAnnot = new AnnotationImpl();
                entityAnnot.setTerm(field.getKey().substring(1));

                value(entityAnnot, field.getValue(), codec);
                if (annotatable != null) {
                    annotatable.getAnnotations().add(entityAnnot);
                }
            } else if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) {
                type = field.getValue().asText();
            } else if (annotation == null && customAnnotation.matches()
                    && !"odata".equals(customAnnotation.group(2))) {
                annotation = new AnnotationImpl();
                annotation.setTerm(customAnnotation.group(2) + "." + customAnnotation.group(3));
                value(annotation, field.getValue(), codec);
            } else {
                final PropertyImpl property = new PropertyImpl();
                property.setName(field.getKey());
                property.setType(
                        type == null ? null : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal());
                type = null;

                value(property, field.getValue(), codec);
                properties.add(property);

                if (annotation != null) {
                    property.getAnnotations().add(annotation);
                    annotation = null;
                }
            }
        }
    }

    private Object fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo) throws EdmPrimitiveTypeException {
        return node.isNull() ? null
                : typeInfo == null ? node.asText()
                        : typeInfo.getPrimitiveTypeKind().isGeospatial()
                                ? getGeoDeserializer().deserialize(node, typeInfo)
                                : ((EdmPrimitiveType) typeInfo.getType()).valueOfString(node.asText(), true, null,
                                        Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, true,
                                        ((EdmPrimitiveType) typeInfo.getType()).getDefaultType());
    }

    private Object fromComplex(final ObjectNode node, final ObjectCodec codec)
            throws IOException, EdmPrimitiveTypeException {

        if (version.compareTo(ODataServiceVersion.V40) < 0) {
            final List<Property> properties = new ArrayList<Property>();
            populate(null, properties, node, codec);
            return properties;
        } else {
            final LinkedComplexValue linkComplexValue = new LinkedComplexValueImpl();
            final Set<String> toRemove = new HashSet<String>();
            for (final Iterator<Map.Entry<String, JsonNode>> itor = node.fields(); itor.hasNext();) {
                final Map.Entry<String, JsonNode> field = itor.next();

                links(field, linkComplexValue, toRemove, node, codec);
            }
            node.remove(toRemove);

            populate(linkComplexValue, linkComplexValue.getValue(), node, codec);
            return linkComplexValue;
        }
    }

    private void fromCollection(final Valuable valuable, final Iterator<JsonNode> nodeItor,
            final EdmTypeInfo typeInfo, final ObjectCodec codec) throws IOException, EdmPrimitiveTypeException {

        final List<Object> values = new ArrayList<Object>();
        ValueType valueType = ValueType.COLLECTION_PRIMITIVE;

        final EdmTypeInfo type = typeInfo == null ? null
                : new EdmTypeInfo.Builder().setTypeExpression(typeInfo.getFullQualifiedName().toString()).build();

        while (nodeItor.hasNext()) {
            final JsonNode child = nodeItor.next();

            if (child.isValueNode()) {
                if (typeInfo == null || typeInfo.isPrimitiveType()) {
                    final Object value = fromPrimitive(child, type);
                    valueType = value instanceof Geospatial ? ValueType.COLLECTION_GEOSPATIAL
                            : ValueType.COLLECTION_PRIMITIVE;
                    values.add(value);
                } else {
                    valueType = ValueType.COLLECTION_ENUM;
                    values.add(child.asText());
                }
            } else if (child.isContainerNode()) {
                if (child.has(jsonType)) {
                    ((ObjectNode) child).remove(jsonType);
                }
                final Object value = fromComplex((ObjectNode) child, codec);
                valueType = value instanceof LinkedComplexValue ? ValueType.COLLECTION_LINKED_COMPLEX
                        : ValueType.COLLECTION_COMPLEX;
                values.add(value);
            }
        }
        valuable.setValue(valueType, values);
    }

    protected void value(final Valuable valuable, final JsonNode node, final ObjectCodec codec)
            throws IOException, EdmPrimitiveTypeException {

        EdmTypeInfo typeInfo = StringUtils.isBlank(valuable.getType()) ? null
                : new EdmTypeInfo.Builder().setTypeExpression(valuable.getType()).build();

        final Map.Entry<ODataPropertyType, EdmTypeInfo> guessed = guessPropertyType(node);
        if (typeInfo == null) {
            typeInfo = guessed.getValue();
        }

        final ODataPropertyType propType = typeInfo == null ? guessed.getKey()
                : typeInfo.isCollection() ? ODataPropertyType.COLLECTION
                        : typeInfo.isPrimitiveType() ? ODataPropertyType.PRIMITIVE
                                : node.isValueNode() ? ODataPropertyType.ENUM : ODataPropertyType.COMPLEX;

        switch (propType) {
        case COLLECTION:
            fromCollection(valuable, node.elements(), typeInfo, codec);
            break;

        case COMPLEX:
            if (node.has(jsonType)) {
                valuable.setType(node.get(jsonType).asText());
                ((ObjectNode) node).remove(jsonType);
            }
            final Object value = fromComplex((ObjectNode) node, codec);
            valuable.setValue(value instanceof LinkedComplexValue ? ValueType.LINKED_COMPLEX : ValueType.COMPLEX,
                    value);
            break;

        case ENUM:
            valuable.setValue(ValueType.ENUM, node.asText());
            break;

        case PRIMITIVE:
            if (valuable.getType() == null && typeInfo != null) {
                valuable.setType(typeInfo.getFullQualifiedName().toString());
            }
            final Object primitiveValue = fromPrimitive(node, typeInfo);
            valuable.setValue(primitiveValue instanceof Geospatial ? ValueType.GEOSPATIAL : ValueType.PRIMITIVE,
                    primitiveValue);
            break;

        case EMPTY:
        default:
            valuable.setValue(ValueType.PRIMITIVE, StringUtils.EMPTY);
        }
    }

    @Override
    public ResWrap<EntitySet> toEntitySet(final InputStream input) throws ODataDeserializerException {
        try {
            parser = new JsonFactory(new ObjectMapper()).createParser(input);
            return new JsonEntitySetDeserializer(version, serverMode).doDeserialize(parser);
        } catch (final IOException e) {
            throw new ODataDeserializerException(e);
        }
    }

    @Override
    public ResWrap<Entity> toEntity(final InputStream input) throws ODataDeserializerException {
        try {
            parser = new JsonFactory(new ObjectMapper()).createParser(input);
            return new JsonEntityDeserializer(version, serverMode).doDeserialize(parser);
        } catch (final IOException e) {
            throw new ODataDeserializerException(e);
        }
    }

    @Override
    public ResWrap<Property> toProperty(final InputStream input) throws ODataDeserializerException {
        try {
            parser = new JsonFactory(new ObjectMapper()).createParser(input);
            return new JsonPropertyDeserializer(version, serverMode).doDeserialize(parser);
        } catch (final IOException e) {
            throw new ODataDeserializerException(e);
        }
    }

    @Override
    public ODataError toError(final InputStream input) throws ODataDeserializerException {
        try {
            parser = new JsonFactory(new ObjectMapper()).createParser(input);
            return new JsonODataErrorDeserializer(version, serverMode).doDeserialize(parser);
        } catch (final IOException e) {
            throw new ODataDeserializerException(e);
        }
    }
}