cancel
Showing results for 
Search instead for 
Did you mean: 

Custom serialization of complex process variable

ajjain
Champ in-the-making
Champ in-the-making
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.
16 REPLIES 16

ajjain
Champ in-the-making
Champ in-the-making
Any updates Frederik??
BTW when is Activiti next release coming?

ajjain
Champ in-the-making
Champ in-the-making
Hi Frederik,
I wish to use this enhancement in my present application. When can I expect next Activiti release? In case it has more than a months time to come. Can you please suggest me how can I include this in my application without violating license.
Abhishek

frederikherema1
Star Contributor
Star Contributor
Activiti is Apache 2.0 licensed. You can fork it, modify it and basically do whatever you want with it. Checkout the sources, alter what you need and build the engine-jar again (we use maven as a build-system). Ideally, your changes can be turned into a pull-request in githun, so it's easier to review than a bunch of individual files…

ajjain
Champ in-the-making
Champ in-the-making
So including changes in Activiti code and building it again doesn't violate license, is that what you mean?

jbarrez
Star Contributor
Star Contributor
That's right. Activiti is Apache licence. You can do with it what you want.

ajjain
Champ in-the-making
Champ in-the-making
Is this change https://github.com/Activiti/Activiti/commit/e127424d26307881b09fa63f0d2d7bafe0629dec targeted to be released with 5.15 branch on this 15th Jan?
Currently we are on 5.13 branch and are blocked on this change. Shall we wait or pull our changes on 5.13 and move ahead? BTW I have added my changes on top of 5.13 @ https://github.com/ajjain/Activiti/commit/d482372d17865a5c341332372154375eea99de1a

frederikherema1
Star Contributor
Star Contributor
This will be released as part of the 5.15 release. The master on git represent the current release snapshot and will be used to base the final 5.* release on, when the time for release arrives…