package org.activiti.engine.impl.variable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; import org.activiti.engine.ActivitiException; import org.activiti.engine.ActivitiIllegalArgumentException; import org.activiti.engine.impl.context.Context; /** * Variable type capable of storing a list of reference to JPA-entities. Only JPA-Entities which * are configured by annotations are supported. Use of compound primary keys is not supported. * <br> * The variable value should be of type {@link List} and can only contain objects of the same type. * * @author Frederik Heremans */ public class JPAEntityListVariableType implements VariableType, CacheableVariable { public static final String TYPE_NAME = "jpa-entity-list"; protected JPAEntityMappings mappings; protected boolean forceCachedValue = false; public JPAEntityListVariableType() { mappings = new JPAEntityMappings(); } @Override public void setForceCacheable(boolean forceCachedValue) { this.forceCachedValue = forceCachedValue; } @Override public String getTypeName() { return TYPE_NAME; } @Override public boolean isCachable() { return forceCachedValue; } @Override public boolean isAbleToStore(Object value) { boolean canStore = false; if(value instanceof List<?>) { List<?> list = (List<?>) value; if(list.size() > 0) { // We can only store the list if we are sure it's actually a list of JPA entities. In case the // list is empty, we don't store it. canStore = true; Class<?> entityClass = mappings.getEntityMetaData(list.get(0).getClass()).getEntityClass(); for (Object entity : list) { canStore = entity != null && mappings.isJPAEntity(entity) && mappings.getEntityMetaData(entity.getClass()).getEntityClass().equals(entityClass); if (!canStore) { // In case the object is not a JPA entity or the class doesn't match, we can't store the list break; } } } } return canStore; } @Override public void setValue(Object value, ValueFields valueFields) { EntityManagerSession entityManagerSession = Context .getCommandContext() .getSession(EntityManagerSession.class); if (entityManagerSession == null) { throw new ActivitiException("Cannot set JPA variable: " + EntityManagerSession.class + " not configured"); } else { // Before we set the value we must flush all pending changes from the entitymanager // If we don't do this, in some cases the primary key will not yet be set in the object // which will cause exceptions down the road. entityManagerSession.flush(); } if(value instanceof List<?> && ((List<?>) value).size() > 0) { List<?> list = (List<?>) value; List<String> ids = new ArrayList<String>(); String type = mappings.getJPAClassString(list.get(0)); for(Object entry: list) { ids.add(mappings.getJPAIdString(entry)); } // Store type in text field and the ID's as a serialized array valueFields.setBytes(serializeIds(ids)); valueFields.setTextValue(type); } else if(value == null) { valueFields.setBytes(null); valueFields.setTextValue(null); } else { throw new ActivitiIllegalArgumentException("Value is not a list of JPA entities: " + value); } } @Override public Object getValue(ValueFields valueFields) { byte[] bytes = valueFields.getBytes(); if(valueFields.getTextValue() != null && bytes != null) { String entityClass = valueFields.getTextValue(); List<Object> result = new ArrayList<Object>(); String[] ids = deserializeIds(bytes); for(String id : ids) { result.add(mappings.getJPAEntity(entityClass, id)); } return result; } return null; } /** * @return a bytearray containing all ID's in the given string serialized as an array. */ protected byte[] serializeIds(List<String> ids) { try { String[] toStore = ids.toArray(new String[] {}); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(toStore); return baos.toByteArray(); } catch (IOException ioe) { throw new ActivitiException("Unexpected exception when serializing JPA id's", ioe); } } protected String[] deserializeIds(byte[] bytes) { try { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream in = new ObjectInputStream(bais); Object read = in.readObject(); if(!(read instanceof String[])) { throw new ActivitiIllegalArgumentException("Deserialized value is not an array of ID's: " + read); } return (String[]) read; } catch (IOException ioe) { throw new ActivitiException("Unexpected exception when deserializing JPA id's", ioe); } catch (ClassNotFoundException e) { throw new ActivitiException("Unexpected exception when deserializing JPA id's", e); } } }