content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * Licensed 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.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.
   *
  
   */
  public class JPAEntityListVariableType implements VariableType, CacheableVariable {
  
      public static final String TYPE_NAME = "jpa-entity-list";
  
      protected JPAEntityMappings mappings;
  
      protected boolean forceCachedValue;
  
      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);
          }
      }
  }
  