com.turn.shapeshifter.SchemaParser.java Source code

Java tutorial

Introduction

Here is the source code for com.turn.shapeshifter.SchemaParser.java

Source

/**
 * Copyright 2012, 2013 Turn, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.turn.shapeshifter;

import java.util.Iterator;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.Message;

/**
 * Implementation of {@link Parser} based on the configuration contained in a
 * {@link NamedSchema}.
 *
 * @author jsilland
 */
class SchemaParser implements Parser {

    private final NamedSchema schema;

    /**
     * Returns a new parser that will use the    configured schema.
     * 
     * @param schema the schema to use for parsing
     */
    SchemaParser(NamedSchema schema) {
        this.schema = schema;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Message parse(JsonNode node, ReadableSchemaRegistry registry) throws ParsingException {
        Message.Builder builder = DynamicMessage.newBuilder(schema.getDescriptor());

        for (Map.Entry<String, FieldDescriptor> fieldEntry : schema.getFields().entrySet()) {
            String fieldName = schema.getPropertyName(fieldEntry.getKey());
            FieldDescriptor field = fieldEntry.getValue();
            if (node.has(fieldName) && !node.get(fieldName).isNull()) {
                JsonNode valueNode = node.get(fieldName);
                if (field.isRepeated()) {
                    if (schema.getMappings().containsKey(field.getName())) {
                        parseMappedField(registry, builder, fieldName, field, valueNode);
                    } else {
                        parseRepeatedField(registry, builder, fieldName, field, valueNode);
                    }
                } else {
                    Object value = parseValue(valueNode, field, registry);
                    if (value != null) {
                        builder.setField(field, value);
                    }
                }
            }
        }

        return builder.build();
    }

    /**
     * Parses a repeated mapped field.
     *
     * @param registry a registry of schemas, used for parsing enclosed objects
     * @param builder the builder in which the parsed field should be set
     * @param field the descriptor of the repeated field being parsed
     * @param fieldName the JSON name of the field
     * @param valueNode the JSON node being parsed
     * @throws ParsingException
     * @see {@link #mapRepeatedField(String, String)}
     */
    private void parseMappedField(ReadableSchemaRegistry registry, Message.Builder builder, String fieldName,
            FieldDescriptor field, JsonNode valueNode) throws ParsingException {
        if (!valueNode.isObject()) {
            throw new IllegalArgumentException(
                    "Field '" + fieldName + "' is expected to be an object, but was " + valueNode.asToken());
        }
        ObjectNode objectNode = (ObjectNode) valueNode;
        Iterator<Map.Entry<String, JsonNode>> subObjectsIterator = objectNode.fields();
        while (subObjectsIterator.hasNext()) {
            Map.Entry<String, JsonNode> subObject = subObjectsIterator.next();
            Message message = (Message) parseValue(subObject.getValue(), field, registry);
            DynamicMessage.Builder dynamicMessage = DynamicMessage.newBuilder(field.getMessageType());
            dynamicMessage.mergeFrom(message);
            dynamicMessage.setField(schema.getMappings().get(field.getName()), subObject.getKey());
            builder.addRepeatedField(field, dynamicMessage.build());
        }
    }

    /**
     * Parses a repeated field.
     *
     * @param registry a registry of schemas, used for parsing enclosed objects
     * @param builder the builder in which the parsed field should be set
     * @param fieldName the JSON name of the field
     * @param field the descriptor of the repeated field being parsed
     * @param valueNode the JSON node being parsed
     * @throws ParsingException
     */
    private void parseRepeatedField(ReadableSchemaRegistry registry, Message.Builder builder, String fieldName,
            FieldDescriptor field, JsonNode valueNode) throws ParsingException {
        if (!valueNode.isArray()) {
            throw new IllegalArgumentException(
                    "Field '" + fieldName + "' is expected to be an array, but was " + valueNode.asToken());
        }
        ArrayNode array = (ArrayNode) valueNode;
        if (array.size() != 0) {
            for (JsonNode item : array) {
                Object value = parseValue(item, field, registry);
                if (value != null) {
                    builder.addRepeatedField(field, value);
                }
            }
        }
    }

    /**
     * Reads the value of a JSON node and returns the corresponding Java object.
     *
     * @param jsonNode the node to convert to a Java object
     * @param field the protocol buffer field to which the resulting value will be assigned
     * @param registry a schema registry in which enclosed message fields are expected to
     * be defined
     * @return a Java object representing the field's value
     * @throws ParsingException
     * @throws
     * @throws UnmappableValueException in case the JSON value node cannot be converted
     * to a Java object that could be assigned to {@code field}
     */
    private Object parseValue(JsonNode jsonNode, FieldDescriptor field, ReadableSchemaRegistry registry)
            throws ParsingException {
        Object value = null;
        if (schema.getTransforms().containsKey(field.getName())) {
            return schema.getTransforms().get(field.getName()).parse(jsonNode);
        }
        switch (field.getType()) {
        case BOOL:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_BOOLEAN_TOKENS);
            value = Boolean.valueOf(jsonNode.asBoolean());
            break;
        case BYTES:
            break;
        case DOUBLE:
            value = new Double(jsonNode.asDouble());
            break;
        case ENUM:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_ENUM_TOKENS);
            String enumValue = schema.getEnumCaseFormat().to(NamedSchema.PROTO_ENUM_CASE_FORMAT, jsonNode.asText());
            value = field.getEnumType().findValueByName(enumValue);
            break;
        case FLOAT:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_FLOAT_TOKENS);
            value = new Float(jsonNode.asDouble());
            break;
        case GROUP:
            break;
        case FIXED32:
        case INT32:
        case SFIXED32:
        case SINT32:
        case UINT32:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_INTEGER_TOKENS);
            value = new Integer(jsonNode.asInt());
            break;
        case FIXED64:
        case INT64:
        case SFIXED64:
        case SINT64:
        case UINT64:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_INTEGER_TOKENS);
            value = new Long(jsonNode.asLong());
            break;
        case MESSAGE:
            if (!jsonNode.isObject()) {
                throw new IllegalArgumentException(
                        "Expected to parse object, found value of type " + jsonNode.asToken());
            }
            Schema subSchema = null;
            if (schema.getSubObjectsSchemas().containsKey(field.getName())) {
                String schemaName = schema.getSubObjectsSchemas().get(field.getName());
                if (registry.contains(schemaName)) {
                    subSchema = registry.get(schemaName);
                } else {
                    throw new IllegalStateException();
                }
            } else {
                try {
                    subSchema = registry.get(field.getMessageType());
                } catch (SchemaObtentionException soe) {
                    throw new ParsingException(soe);
                }
            }
            value = subSchema.getParser().parse(jsonNode, registry);
            break;
        case STRING:
            JsonTokens.checkJsonValueConformance(jsonNode, JsonTokens.VALID_STRING_TOKENS);
            value = jsonNode.asText();
            break;
        default:
            break;
        }
        return value;
    }
}