AnsweredAssumed Answered

Custom serialization of complex process variable

Question asked by ajjain on Nov 28, 2013
Latest reply on Jan 6, 2014 by frederikheremans1
I wished to extend Activiti's default object serialization scheme to json based serialization. This was intended to handle changes in process variable. When any change is made in any process variable structure, I had to always clean up the Activiti's database other wise it gives me deserialization issues. I wish to handle this by serializing all changes as JSON and an offline tool to adapt any changes made to the proc var class.

package test.custom.type;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.persistence.entity.VariableInstanceEntity;
import org.activiti.engine.impl.variable.ByteArrayType;
import org.activiti.engine.impl.variable.ValueFields;
import org.activiti.engine.impl.context.Context;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import test.custom.CustomProcVar;

/**
* @author ajjain
*
* Represents Custom variable type in Activiti. This supports serializing process variables as JSON strings
* as byte arrays.
*/
public class CustomVariableType extends ByteArrayType {

   /** The type name. */
   protected String typeName;

   /** The class. */
   protected Class<? extends CustomProcVar> theClass;
   /**
    * represents the JSON mapper object
    */
   private static final ObjectMapper jsonMapper = new ObjectMapper();
   /**
    * Instantiates a new view object variable type.
    *
    * @param typeName the type name
    * @param theClass the the class
    */
   public CustomVariableType(String typeName, Class<? extends CustomProcVar> theClass) {
      logger.trace("constructing view object variable type name {}, class {}", typeName, theClass);
      this.typeName = typeName;
      this.theClass = theClass;
   }
   /**
    * @return the typeName
    */
   @Override
   public String getTypeName() {
      return typeName;
   }

   @Override
   public Object getValue(ValueFields valueFields) {
      logger.trace("CustomVariableType.getValue valueFields {}", valueFields);

      Object cachedObject = valueFields.getCachedValue();
      if (cachedObject != null) {
         return cachedObject;
      }

      byte[] bytes = (byte[]) super.getValue(valueFields);

      Object deserializedObject;
      try {
         deserializedObject = jsonMapper.readValue(bytes, this.getTheClass());
         logger.trace("getValue returning object {}", deserializedObject);
         valueFields.setCachedValue(deserializedObject);

         if (valueFields instanceof VariableInstanceEntity) {
            Context.getCommandContext()
            .getDbSqlSession()
            .addDeserializedObject(bytes, bytes, (VariableInstanceEntity) valueFields);
         }

         return deserializedObject;
      }
      catch (Throwable e) {
         logger.warn("error in deserializing the output", e);
         throw new ActivitiException("Couldn't deserialize object in variable '"+valueFields.getName()+"'", e);
      }
   }
   @Override
   public boolean isCachable() {
      return true;
   }
   @Override
   public boolean isAbleToStore(Object value) {
      logger.trace("isAbleToStore value {}", value, value instanceof CustomProcVar);
      return (value instanceof CustomProcVar) && (theClass.isAssignableFrom(value.getClass()));
   }

   @Override
   public void setValue(Object value, ValueFields valueFields) {
      byte[] byteArray = serialize(value, valueFields);
      valueFields.setCachedValue(value);

      if (valueFields.getBytes() == null) {
         if (valueFields instanceof VariableInstanceEntity) {
            Context.getCommandContext()
            .getDbSqlSession()
            .addDeserializedObject(byteArray, byteArray, (VariableInstanceEntity)valueFields);
         }
      }
      super.setValue(byteArray, valueFields);  
   }

   /**
    * Serialize.
    *
    * @param value the value
    * @param valueFields the value fields
    * @return the byte[]
    */
   public static byte[] serialize(Object value, ValueFields valueFields) {
      if (value == null) {
         return null;
      }
      try {
         byte[] valueBytes = jsonMapper.writeValueAsBytes(value);

         return valueBytes;
      } catch (Exception e) {
         throw new ActivitiException("Couldn't serialize value '"+value+"' in variable '"+valueFields.getName()+"'", e);
      }
   }
   /**
    * @return the theClass
    */
   public Class<?> getTheClass() {
      return theClass;
   }

   /**
    * @param theClass the theClass to set
    */
   public void setTheClass(Class<? extends CustomProcVar> theClass) {
      this.theClass = theClass;
   }

   /**
    * @param typeName the typeName to set
    */
   public void setTypeName(String typeName) {
      this.typeName = typeName;
   }

   /** The logger. */
   private Logger logger = LoggerFactory.getLogger(CustomVariableType.class);
   /**
    * @return the json mapper
    */
   public static ObjectMapper getJsonmapper() {
      return jsonMapper;
   }

}


After integrating the above code with the application, I found issues with getValue method. On further analysis of issue, I realized it is DeserializedObject class which defaults to serializing objects using default mechanism. Also, this doesn't open any extension points so that the same can be used for any custom serialization mechanism.
I wanted to bring this issue on the community so that this feature can be contributed. I am finding this to be very useful feature which every Activiti user will wish for applications deployed in production.

Outcomes