Package ca.jeb.protobuf

Source Code of ca.jeb.protobuf.ProtobufSerializer

// Copyright (c) 2014 Erick Bourgeois, All Rights Reserved

package ca.jeb.protobuf;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.jeb.common.infra.JException;
import ca.jeb.common.infra.JReflectionUtils;
import ca.jeb.common.infra.JStringUtils;
import ca.jeb.protobuf.converter.NullConverter;
import ca.jeb.protobuf.internal.ProtobufSerializerUtils;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;

/**
* This class' has two methods for serialization:
* <ol>
* <li>toProtobuf: serialize to a Protobuf
* <li>fromProtobuf: serialize from a Protobuf object
* </ol>
* This class can be used as Java bean and thus a singleton.
*
* @author <a href="mailto:erick@jeb.ca">Erick Bourgeois</a>
*/
public class ProtobufSerializer implements IProtobufSerializer
{
  private static final Logger LOGGER = LoggerFactory.getLogger(ProtobufSerializer.class);

  /**
   * @see ca.jeb.protobuf.IProtobufSerializer#toProtobuf(java.lang.Object)
   */
  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public Message toProtobuf(Object pojo) throws ProtobufException
  {
    try
    {
      final Class<?> fromClazz = (Class<?>)pojo.getClass();
      final Class<? extends GeneratedMessage> protoClazz = ProtobufSerializerUtils.getProtobufClassFromPojoAnno(fromClazz);
      if (protoClazz == null)
      {
        throw new ProtobufAnnotationException("Doesn't seem like " + fromClazz + " is ProtobufEntity");
      }

      final Map<Field, ProtobufAttribute> protoBufFields = ProtobufSerializerUtils.getAllProtbufFields(fromClazz);
      if (protoBufFields.isEmpty())
      {
        return null;
      }

      final Method newBuilderMethod = protoClazz.getMethod("newBuilder");
      final Builder protoObjBuilder = (Builder)newBuilderMethod.invoke(null);
      for (Entry<Field, ProtobufAttribute> entry : protoBufFields.entrySet())
      {
        final Field field = entry.getKey();
        final ProtobufAttribute gpbAnnotation = entry.getValue();
        final String fieldName = field.getName();

        // 1. Determine validity of value
        Object value = getPojoFieldValue(pojo, gpbAnnotation, field);
        // If value is null and it is not required, skip, as the default for Protobuf values is null
        if (value == null)
        {
          continue;
        }

        // 2. Call recursively if this is a ProtobufEntity
        value = serializeToProtobufEntity(value);

        // 3. Handle POJO Collections/Lists
        if (value instanceof Collection)
        {
          value = convertCollectionToProtobufs((Collection<Object>)value);
          if (((Collection)value).isEmpty())
          {
            continue;
          }
        }

        // 4. Determine the setter name
        final String setter = ProtobufSerializerUtils.getProtobufSetter(gpbAnnotation, field, value);

        // 5. Finally, set the value on the Builder
        setProtobufFieldValue(gpbAnnotation, protoObjBuilder, setter, value);
      }

      return protoObjBuilder.build();

    }
    catch (Exception e)
    {
      throw new ProtobufException("Could not generate Protobuf object for " + pojo.getClass() + ": " + e, e);
    }
  }

  /**
   * @see ca.jeb.protobuf.IProtobufSerializer#fromProtobuf(Message, Class)
   */
  @Override
  public Object fromProtobuf(Message protobuf, Class<?> pojoClazz) throws ProtobufException
  {
    try
    {
      final Class<? extends GeneratedMessage> protoClazz = ProtobufSerializerUtils.getProtobufClassFromPojoAnno(pojoClazz);
      if (protoClazz == null)
      {
        throw new ProtobufAnnotationException("Doesn't seem like " + pojoClazz + " is ProtobufEntity");
      }

      final Map<Field, ProtobufAttribute> protobufFields = ProtobufSerializerUtils.getAllProtbufFields(pojoClazz);
      if (protobufFields.isEmpty())
      {
        throw new ProtobufException("No protoBuf fields have been annotated on the class " + pojoClazz + ", thus cannot continue.");
      }

      Object pojo = pojoClazz.newInstance();

      for (Entry<Field, ProtobufAttribute> entry : protobufFields.entrySet())
      {
        final Field field = entry.getKey();
        final ProtobufAttribute protobufAttribute = entry.getValue();
        final String setter = ProtobufSerializerUtils.getPojoSetter(protobufAttribute, field);

        Object protobufValue = getProtobufFieldValue(protobuf, protobufAttribute, field);
        if (protobufValue == null)
        {
          continue;
        }

        setPojoFieldValue(pojo, setter, protobufValue, protobufAttribute);
      }

      return pojo;
    }
    catch (Exception e)
    {
      throw new ProtobufException("Could not generate POJO of type " + pojoClazz + " from Protobuf object " + protobuf.getClass() + ": "
              + e, e);
    }
  }

  /**
   * Returns the field value from the supplied <code>pojo</code> object. If a <code>pojoGetter</code has been set in the
   * {@link ProtobufAttribute}, then use that, otherwise, try getting the field value directly.
   *
   * @param pojo - POJO Object
   * @param protobufAttribute
   * @param field
   * @return Object - the resulting object after calling the POJO's getter
   * @throws ProtobufAnnotationException
   */
  private static final Object getPojoFieldValue(Object pojo, ProtobufAttribute protobufAttribute, Field field)
          throws ProtobufAnnotationException
  {
    final String getter = protobufAttribute.pojoGetter();

    Object value = null;
    if (!getter.isEmpty())
    {
      try
      {
        return JReflectionUtils.runMethod(pojo, getter);
      }
      catch (Exception e)
      {
        throw new ProtobufAnnotationException("Could not get a value for field " + field.getName() + " using configured getter of "
                + getter, e);
      }
    }

    try
    {
      value = JReflectionUtils.runGetter(pojo, field);
    }
    catch (Exception ee)
    {
      throw new ProtobufAnnotationException("Could not execute getter " + getter + " on class " + pojo.getClass().getCanonicalName() + ": "
              + ee, ee);
    }

    if (value == null && protobufAttribute.required())
    {
      throw new ProtobufAnnotationException("Required field " + field.getName() + " on class " + pojo.getClass().getCanonicalName()
              + " is null");
    }

    return value;
  }

  @SuppressWarnings("rawtypes")
  private static final Object getProtobufFieldValue(Message protoBuf, ProtobufAttribute protobufAttribute, Field field) throws JException,
          InstantiationException, IllegalAccessException
  {
    final String fieldName = field.getName();
    final String upperCaseFirstFieldName = JStringUtils.upperCaseFirst(fieldName);
    final String getter = ProtobufSerializerUtils.getProtobufGetter(protobufAttribute, field);
    // This is used to determine if the Protobuf message has populated this value

    Boolean isCollection = Boolean.FALSE;
    if (Collection.class.isAssignableFrom(field.getType()))
    {
      isCollection = Boolean.TRUE;
    }
    if (!isCollection)
    {
      // no need to continue if this field in the protoBuf is not set
      final String haser = StringUtils.replaceOnce(getter, "get", "has");

      final boolean fieldIsSet = (boolean)JReflectionUtils.runMethod(protoBuf, haser, (Object[])null);
      if (!fieldIsSet)
      {
        return null;
      }
    }

    // Go ahead and fun the getter
    Object protobufValue = JReflectionUtils.runMethod(protoBuf, getter, (Object[])null);
    if (isCollection && ((Collection)protobufValue).isEmpty())
    {
      return null;
    }

    // If the field itself is a ProtbufEntity, serialize that!
    if (protobufValue instanceof GeneratedMessage && ProtobufSerializerUtils.isProtbufEntity(field.getType()))
    {
      protobufValue = serializeFromProtobufEntity((Message)protobufValue, field.getType());
    }

    if (protobufValue instanceof Collection)
    {
      protobufValue = convertCollectionFromProtobufs(field, (Collection<?>)protobufValue);
      if (((Collection)protobufValue).isEmpty())
      {
        return null;
      }
    }

    return protobufValue;
  }

  /**
   * Create a new serializer and serializes the supplied object/attribute.
   *
   * @param pojo - the POJO object
   * @return Object - the resulting GPB object
   * @throws JException
   */
  private static final Object serializeToProtobufEntity(Object pojo) throws JException
  {
    final ProtobufEntity protoBufEntity = ProtobufSerializerUtils.getProtobufEntity(pojo.getClass());

    if (protoBufEntity == null)
    {
      return pojo;
    }

    return new ProtobufSerializer().toProtobuf(pojo);
  }

  /**
   * Create a new serializer and (de)serializes the supplied Protobu/attribute to a POJO of type <i>pojoClazz</i>.
   *
   * @param protoBuf
   * @param pojoClazz
   * @return
   * @throws JException
   */
  private static final Object serializeFromProtobufEntity(Message protoBuf, Class<?> pojoClazz) throws JException
  {
    final ProtobufEntity protoBufEntity = ProtobufSerializerUtils.getProtobufEntity(pojoClazz);

    if (protoBufEntity == null)
    {
      return protoBuf;
    }

    return new ProtobufSerializer().fromProtobuf(protoBuf, pojoClazz);
  }

  /**
   * Loops through the collection of objects and serializes them, iff they have ProtobufEntity annotations.
   *
   * @param value - Collection<Object>
   * @return Object - Collection of serialized objects.
   * @throws JException
   */
  private static final Object convertCollectionToProtobufs(Collection<Object> collectionOfNonProtobufs) throws JException
  {
    if (collectionOfNonProtobufs.isEmpty())
    {
      return collectionOfNonProtobufs;
    }
    final Object first = collectionOfNonProtobufs.toArray()[0];
    if (!ProtobufSerializerUtils.isProtbufEntity(first))
    {
      return collectionOfNonProtobufs;
    }

    final Collection<Object> newCollectionValues;

    /**
     * Maintain the Collection type of value at this stage (if it is a Set), and if conversion is required to a
     * different Collection type, that will be handled by a converter later on
     */
    if (collectionOfNonProtobufs instanceof Set)
    {
      newCollectionValues = new HashSet<>();
    }
    else
    {
      newCollectionValues = new ArrayList<>();
    }

    for (Object iProtobufGenObj : collectionOfNonProtobufs)
    {
      newCollectionValues.add(serializeToProtobufEntity(iProtobufGenObj));
    }

    return newCollectionValues;
  }

  private static Object convertCollectionFromProtobufs(Field field, Collection<?> collectionOfProtobufs) throws JException,
          InstantiationException, IllegalAccessException
  {
    if (collectionOfProtobufs.isEmpty())
    {
      return collectionOfProtobufs;
    }

    final ParameterizedType listType = (ParameterizedType)field.getGenericType();
    final Class<?> collectionClazzType = (Class<?>)listType.getActualTypeArguments()[0];
    final ProtobufEntity protoBufEntityAnno = ProtobufSerializerUtils.getProtobufEntity(collectionClazzType);

    final Object first = collectionOfProtobufs.toArray()[0];
    if (!(first instanceof GeneratedMessage) && protoBufEntityAnno == null)
    {
      return collectionOfProtobufs;
    }

    final Collection<Object> newCollectionOfValues = new ArrayList<>();
    for (Object protobufValue : collectionOfProtobufs)
    {
      if (!(protobufValue instanceof GeneratedMessage))
      {
        throw new ProtobufException("Collection contains an object of type " + protobufValue.getClass()
                + " which is not an instanceof GeneratedMessage, can not (de)serialize this");
      }
      newCollectionOfValues.add(serializeFromProtobufEntity((Message)protobufValue, collectionClazzType));
    }

    return newCollectionOfValues;
  }

  /**
   * This method does the actual "set" on the Protobuf builder. If the user specified a converter,
   * then use that right before we actually try and set the value.
   *
   * @param protobufAttribute - ProtobufAttribute
   * @param protoObjBuilder - G.Builder
   * @param setter - String
   * @param fieldValue - POJO's filed Object
   * @throws NoSuchMethodException
   * @throws SecurityException
   * @throws ProtobufAnnotationException
   * @throws InstantiationException
   * @throws IllegalAccessException
   * @throws IllegalArgumentException
   * @throws InvocationTargetException
   */
  private static final void setProtobufFieldValue(ProtobufAttribute protobufAttribute, Builder protoObjBuilder, String setter,
          Object fieldValue) throws NoSuchMethodException, SecurityException, ProtobufAnnotationException, InstantiationException,
          IllegalAccessException, IllegalArgumentException, InvocationTargetException
  {
    Class<? extends Object> fieldValueClass = fieldValue.getClass();
    Class<? extends Object> gpbClass = fieldValueClass;

    final Class<? extends IProtobufConverter> converterClazz = protobufAttribute.converter();
    if (converterClazz != NullConverter.class)
    {
      final IProtobufConverter protoBufConverter = (IProtobufConverter)converterClazz.newInstance();
      fieldValue = protoBufConverter.convertToProtobuf(fieldValue);
      gpbClass = fieldValue.getClass();
      fieldValueClass = gpbClass;
    }

    // Need to convert the argument class from non-primitives to primitives, as Protobuf uses these.
    gpbClass = ProtobufSerializerUtils.getProtobufClass(fieldValue, gpbClass);

    final Method gpbMethod = protoObjBuilder.getClass().getDeclaredMethod(setter, gpbClass);
    gpbMethod.invoke(protoObjBuilder, fieldValue);
  }

  /**
   * This method does the actual "set" on the POJO instance. If the user specified a converter,
   * then use that right before we actually try and set the value.
   *
   * @param pojo - the instance of the POJO
   * @param setter - the setter method name
   * @param protobufValue - the Protobuf value, note this could be anything from another ProtobufEntity to a primitive, like boolean
   * @param protobufAttribute - the ProtobufAttribute annotation
   * @throws InstantiationException
   * @throws IllegalAccessException
   * @throws JException
   */
  private static final void setPojoFieldValue(Object pojo, String setter, Object protobufValue, ProtobufAttribute protobufAttribute)
          throws InstantiationException, IllegalAccessException, JException
  {
    /**
     * convertCollectionFromProtoBufs() above returns an ArrayList, and we may have a converter to convert to a Set,
     * so we are performing the conversion there
     */
    final Class<? extends IProtobufConverter> fromProtoBufConverter = protobufAttribute.converter();
    if (fromProtoBufConverter != NullConverter.class)
    {
      final IProtobufConverter converter = fromProtoBufConverter.newInstance();
      protobufValue = converter.convertFromProtobuf(protobufValue);
    }

    Class<? extends Object> argClazz = protobufValue.getClass();

    JReflectionUtils.runSetter(pojo, setter, protobufValue, argClazz);
  }
}
TOP

Related Classes of ca.jeb.protobuf.ProtobufSerializer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.