Java tutorial
/* * * 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.airavata.db; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.base.CaseFormat; import org.apache.thrift.TBase; import org.apache.thrift.TException; import org.apache.thrift.TFieldIdEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.Iterator; import java.util.Map; /** * This abstract class represents a generic de-serializer for converting JSON to Thrift-based entities. * * @param <E> An implementation of the {@link org.apache.thrift.TFieldIdEnum} interface. * @param <T> An implementation of the {@link org.apache.thrift.TBase} interface. */ public abstract class AbstractThriftDeserializer<E extends TFieldIdEnum, T extends TBase<T, E>> extends JsonDeserializer<T> { private static Logger log = LoggerFactory.getLogger(AbstractThriftDeserializer.class); @Override public T deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException { final T instance = newInstance(); final ObjectMapper mapper = (ObjectMapper) jp.getCodec(); final ObjectNode rootNode = mapper.readTree(jp); final Iterator<Map.Entry<String, JsonNode>> iterator = rootNode.fields(); while (iterator.hasNext()) { final Map.Entry<String, JsonNode> currentField = iterator.next(); try { /* * If the current node is not a null value, process it. Otherwise, * skip it. Jackson will treat the null as a 0 for primitive * number types, which in turn will make Thrift think the field * has been set. Also we ignore the MongoDB specific _id field */ if (!currentField.getKey().equalsIgnoreCase("_id") && currentField.getValue().getNodeType() != JsonNodeType.NULL) { final E field = getField( CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, currentField.getKey())); final JsonParser parser = currentField.getValue().traverse(); parser.setCodec(mapper); final Object value = mapper.readValue(parser, generateValueType(instance, field)); if (value != null) { log.debug(String.format("Field %s produced value %s of type %s.", currentField.getKey(), value, value.getClass().getName())); instance.setFieldValue(field, value); } else { log.debug("Field {} contains a null value. Skipping...", currentField.getKey()); } } else { log.debug("Field {} contains a null value. Skipping...", currentField.getKey()); } } catch (final NoSuchFieldException | IllegalArgumentException e) { log.error("Unable to de-serialize field '{}'.", currentField.getKey(), e); ctxt.mappingException(e.getMessage()); } } try { // Validate that the instance contains all required fields. validate(instance); } catch (final TException e) { log.error(String.format("Unable to deserialize JSON '%s' to type '%s'.", jp.getValueAsString(), instance.getClass().getName(), e)); ctxt.mappingException(e.getMessage()); } return instance; } /** * Returns the {@code <E>} enumerated value that represents the target * field in the Thrift entity referenced in the JSON document. * @param fieldName The name of the Thrift entity target field. * @return The {@code <E>} enumerated value that represents the target * field in the Thrift entity referenced in the JSON document. */ protected abstract E getField(String fieldName); /** * Creates a new instance of the Thrift entity class represented by this deserializer. * @return A new instance of the Thrift entity class represented by this deserializer. */ protected abstract T newInstance(); /** * Validates that the Thrift entity instance contains all required fields after deserialization. * @param instance A Thrift entity instance. * @throws org.apache.thrift.TException if unable to validate the instance. */ protected abstract void validate(T instance) throws TException; /** * Generates a {@link JavaType} that matches the target Thrift field represented by the provided * {@code <E>} enumerated value. If the field's type includes generics, the generics will * be added to the generated {@link JavaType} to support proper conversion. * @param thriftInstance The Thrift-generated class instance that will be converted to/from JSON. * @param field A {@code <E>} enumerated value that represents a field in a Thrift-based entity. * @return The {@link JavaType} representation of the type associated with the field. * @throws NoSuchFieldException if unable to determine the field's type. * @throws SecurityException if unable to determine the field's type. */ protected JavaType generateValueType(final T thriftInstance, final E field) throws NoSuchFieldException, SecurityException { final TypeFactory typeFactory = TypeFactory.defaultInstance(); final Field declaredField = thriftInstance.getClass().getDeclaredField(field.getFieldName()); if (declaredField.getType().equals(declaredField.getGenericType())) { log.debug("Generating JavaType for type '{}'.", declaredField.getType()); return typeFactory.constructType(declaredField.getType()); } else { final ParameterizedType type = (ParameterizedType) declaredField.getGenericType(); final Class<?>[] parameterizedTypes = new Class<?>[type.getActualTypeArguments().length]; for (int i = 0; i < type.getActualTypeArguments().length; i++) { parameterizedTypes[i] = (Class<?>) type.getActualTypeArguments()[i]; } log.debug("Generating JavaType for type '{}' with generics '{}'", declaredField.getType(), parameterizedTypes); return typeFactory.constructParametricType(declaredField.getType(), parameterizedTypes); } } }