Package org.activiti.engine.impl.db

Source Code of org.activiti.engine.impl.db.DbSqlSession

/* 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.db;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiOptimisticLockingException;
import org.activiti.engine.ActivitiWrongDbException;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.ActivitiVariableEvent;
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.DeploymentQueryImpl;
import org.activiti.engine.impl.ExecutionQueryImpl;
import org.activiti.engine.impl.GroupQueryImpl;
import org.activiti.engine.impl.HistoricActivityInstanceQueryImpl;
import org.activiti.engine.impl.HistoricDetailQueryImpl;
import org.activiti.engine.impl.HistoricProcessInstanceQueryImpl;
import org.activiti.engine.impl.HistoricTaskInstanceQueryImpl;
import org.activiti.engine.impl.HistoricVariableInstanceQueryImpl;
import org.activiti.engine.impl.JobQueryImpl;
import org.activiti.engine.impl.ModelQueryImpl;
import org.activiti.engine.impl.Page;
import org.activiti.engine.impl.ProcessDefinitionQueryImpl;
import org.activiti.engine.impl.ProcessInstanceQueryImpl;
import org.activiti.engine.impl.TaskQueryImpl;
import org.activiti.engine.impl.UserQueryImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.db.upgrade.DbUpgradeStep;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.persistence.entity.PropertyEntity;
import org.activiti.engine.impl.persistence.entity.VariableInstanceEntity;
import org.activiti.engine.impl.util.IoUtil;
import org.activiti.engine.impl.util.ReflectUtil;
import org.activiti.engine.impl.variable.DeserializedObject;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/** responsibilities:
*   - delayed flushing of inserts updates and deletes
*   - optional dirty checking
*   - db specific statement name mapping
*  
* @author Tom Baeyens
* @author Joram Barrez
*/
public class DbSqlSession implements Session {
 
  private static final Logger log = LoggerFactory.getLogger(DbSqlSession.class);
 
  private static final Pattern CLEAN_VERSION_REGEX = Pattern.compile("\\d\\.\\d*");
 
  private static final List<ActivitiVersion> ACTIVITI_VERSIONS = new ArrayList<ActivitiVersion>();
  static {
   
    /* Previous */
   
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.7"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.8"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.9"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.10"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.11"));
   
    // 5.12.1 was a bugfix release on 5.12 and did NOT change the version in ACT_GE_PROPERTY
    // On top of that, DB2 create script for 5.12.1 was shipped with a 'T' suffix ...
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.12", Arrays.asList("5.12.1", "5.12T")));
   
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.13"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.14"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.15"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.15.1"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16.1"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16.2-SNAPSHOT"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16.2"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16.3.0"));
    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.16.4.0"));

    ACTIVITI_VERSIONS.add(new ActivitiVersion("5.17.0.0"));
   
    /* Current */
    ACTIVITI_VERSIONS.add(new ActivitiVersion(ProcessEngine.VERSION));
  }

  protected SqlSession sqlSession;
  protected DbSqlSessionFactory dbSqlSessionFactory;
  protected List<PersistentObject> insertedObjects = new ArrayList<PersistentObject>();
  protected Map<Class<?>, Map<String, CachedObject>> cachedObjects = new HashMap<Class<?>, Map<String,CachedObject>>();
  protected List<DeleteOperation> deleteOperations = new ArrayList<DeleteOperation>();
  protected List<DeserializedObject> deserializedObjects = new ArrayList<DeserializedObject>();
  protected String connectionMetadataDefaultCatalog;
  protected String connectionMetadataDefaultSchema;
 
  protected boolean isOptimizeDeleteOperationsEnabled;

  public DbSqlSession(DbSqlSessionFactory dbSqlSessionFactory) {
    this.dbSqlSessionFactory = dbSqlSessionFactory;
    this.isOptimizeDeleteOperationsEnabled = dbSqlSessionFactory.isOptimizeDeleteOperationsEnabled();
    this.sqlSession = dbSqlSessionFactory
      .getSqlSessionFactory()
      .openSession();
  }

  public DbSqlSession(DbSqlSessionFactory dbSqlSessionFactory, Connection connection, String catalog, String schema) {
    this.dbSqlSessionFactory = dbSqlSessionFactory;
    this.sqlSession = dbSqlSessionFactory
      .getSqlSessionFactory()
      .openSession(connection);
    this.connectionMetadataDefaultCatalog = catalog;
    this.connectionMetadataDefaultSchema = schema;
  }
 
  // Touch  ///////////////////////////////////////////////////////////////////
  // brings the given persistenObject to the top if it already exists
  public void touch(PersistentObject persistentObject) {
    if (persistentObject.getId()==null) {
      throw new ActivitiException("Cannot touch " + persistentObject.getClass() + " with no id");
    }
    if (insertedObjects.contains(persistentObject)) {
      insertedObjects.remove(persistentObject);
      insertedObjects.add(persistentObject);
      cachePut(persistentObject, false);
    }
   }
   
  // insert ///////////////////////////////////////////////////////////////////
 
 
  public void insert(PersistentObject persistentObject) {
    if (persistentObject.getId()==null) {
      String id = dbSqlSessionFactory.getIdGenerator().getNextId()
      persistentObject.setId(id);
    }
    insertedObjects.add(persistentObject);
    cachePut(persistentObject, false);
  }
 
  // update ///////////////////////////////////////////////////////////////////
 
  public void update(PersistentObject persistentObject) {
    cachePut(persistentObject, false);
  }
 
  public int update(String statement, Object parameters) {
     String updateStatement = dbSqlSessionFactory.mapStatement(statement);
     return getSqlSession().update(updateStatement, parameters);
  }
 
  // delete ///////////////////////////////////////////////////////////////////

  public void delete(String statement, Object parameter) {
    deleteOperations.add(new BulkDeleteOperation(statement, parameter));
  }
 
  public void delete(PersistentObject persistentObject) {
    for (DeleteOperation deleteOperation: deleteOperations) {
        if (deleteOperation.sameIdentity(persistentObject)) {
          log.debug("skipping redundant delete: {}", persistentObject);
          return; // Skip this delete. It was already added.
        }
    }
   
    deleteOperations.add(new CheckedDeleteOperation(persistentObject));
  }

  public interface DeleteOperation {
   
    boolean sameIdentity(PersistentObject other);

    void clearCache();
   
    void execute();
   
  }

  /**
   * Use this {@link DeleteOperation} to execute a dedicated delete statement.
   * It is important to note there won't be any optimistic locking checks done
   * for these kind of delete operations!
   *
   * For example, a usage of this operation would be to delete all variables for
   * a certain execution, when that certain execution is removed. The optimistic locking
   * happens on the execution, but the variables can be removed by a simple
   * 'delete from var_table where execution_id is xxx'. It could very well be there
   * are no variables, which would also work with this query, but not with the
   * regular {@link CheckedDeleteOperation}.
   */
  public class BulkDeleteOperation implements DeleteOperation {
    private String statement;
    private Object parameter;
   
    public BulkDeleteOperation(String statement, Object parameter) {
      this.statement = dbSqlSessionFactory.mapStatement(statement);
      this.parameter = parameter;
    }
   
    @Override
    public boolean sameIdentity(PersistentObject other) {
      // this implementation is unable to determine what the identity of the removed object(s) will be.
      return false;
    }

    @Override
    public void clearCache() {
      // this implementation cannot clear the object(s) to be removed from the cache.
    }
   
    @Override
    public void execute() {
      sqlSession.delete(statement, parameter);
    }
   
    @Override
    public String toString() {
      return "bulk delete: " + statement + "(" + parameter + ")";
    }
  }
 
  /**
   * A {@link DeleteOperation} that checks for concurrent modifications if the persistent object implements {@link HasRevision}.
   * That is, it employs optimisting concurrency control. Used when the persistent object has been fetched already.
   */
  public class CheckedDeleteOperation implements DeleteOperation {
    protected final PersistentObject persistentObject;
   
    public CheckedDeleteOperation(PersistentObject persistentObject) {
      this.persistentObject = persistentObject;
    }
   
    @Override
    public boolean sameIdentity(PersistentObject other) {
      return persistentObject.getClass().equals(other.getClass())
          && persistentObject.getId().equals(other.getId());
    }

    @Override
    public void clearCache() {
      cacheRemove(persistentObject.getClass(), persistentObject.getId());
    }
   
    public void execute() {
      String deleteStatement = dbSqlSessionFactory.getDeleteStatement(persistentObject.getClass());
      deleteStatement = dbSqlSessionFactory.mapStatement(deleteStatement);
      if (deleteStatement == null) {
        throw new ActivitiException("no delete statement for " + persistentObject.getClass() + " in the ibatis mapping files");
      }
     
      // It only makes sense to check for optimistic locking exceptions for objects that actually have a revision
      if (persistentObject instanceof HasRevision) {
        int nrOfRowsDeleted = sqlSession.delete(deleteStatement, persistentObject);
        if (nrOfRowsDeleted == 0) {
          throw new ActivitiOptimisticLockingException(persistentObject + " was updated by another transaction concurrently");
        }
      } else {
        sqlSession.delete(deleteStatement, persistentObject);
      }
    }

    public PersistentObject getPersistentObject() {
      return persistentObject;
    }

    @Override
    public String toString() {
      return "delete " + persistentObject;
    }
  }
 
 
  /**
   * A bulk version of the {@link CheckedDeleteOperation}.
   */
  public class BulkCheckedDeleteOperation implements DeleteOperation {
   
    protected Class<? extends PersistentObject> persistentObjectClass;
    protected List<PersistentObject> persistentObjects = new ArrayList<PersistentObject>();
   
    public BulkCheckedDeleteOperation(Class<? extends PersistentObject> persistentObjectClass) {
      this.persistentObjectClass = persistentObjectClass;
    }
   
    public void addPersistentObject(PersistentObject persistentObject) {
      persistentObjects.add(persistentObject);
    }
   
    @Override
    public boolean sameIdentity(PersistentObject other) {
      for (PersistentObject persistentObject : persistentObjects) {
        if (persistentObject.getClass().equals(other.getClass()) && persistentObject.getId().equals(other.getId())) {
          return true;
        }
      }
      return false;
    }

    @Override
    public void clearCache() {
      for (PersistentObject persistentObject : persistentObjects) {
        cacheRemove(persistentObject.getClass(), persistentObject.getId());
      }
    }
   
    public void execute() {
     
      if (persistentObjects.isEmpty()) {
        return;
      }
     
      String bulkDeleteStatement = dbSqlSessionFactory.getBulkDeleteStatement(persistentObjectClass);
      bulkDeleteStatement = dbSqlSessionFactory.mapStatement(bulkDeleteStatement);
      if (bulkDeleteStatement == null) {
        throw new ActivitiException("no bulk delete statement for " + persistentObjectClass + " in the mapping files");
      }
     
      // It only makes sense to check for optimistic locking exceptions for objects that actually have a revision
      if (persistentObjects.get(0) instanceof HasRevision) {
        int nrOfRowsDeleted = sqlSession.delete(bulkDeleteStatement, persistentObjects);
        if (nrOfRowsDeleted < persistentObjects.size()) {
          throw new ActivitiOptimisticLockingException("One of the entities " + persistentObjectClass
              + " was updated by another transaction concurrently while trying to do a bulk delete");
        }
      } else {
        sqlSession.delete(bulkDeleteStatement, persistentObjects);
      }
    }
   
    public Class<? extends PersistentObject> getPersistentObjectClass() {
      return persistentObjectClass;
    }

    public void setPersistentObjectClass(
        Class<? extends PersistentObject> persistentObjectClass) {
      this.persistentObjectClass = persistentObjectClass;
    }

    public List<PersistentObject> getPersistentObjects() {
      return persistentObjects;
    }

    public void setPersistentObjects(List<PersistentObject> persistentObjects) {
      this.persistentObjects = persistentObjects;
    }

    @Override
    public String toString() {
      return "bulk delete of " + persistentObjects.size() + (!persistentObjects.isEmpty() ? " entities of " + persistentObjects.get(0).getClass() : 0 );
    }
  }
 
  // select ///////////////////////////////////////////////////////////////////

  @SuppressWarnings({ "rawtypes" })
  public List selectList(String statement) {
    return selectList(statement, null, 0, Integer.MAX_VALUE);
  }
 
  @SuppressWarnings("rawtypes")
  public List selectList(String statement, Object parameter) { 
    return selectList(statement, parameter, 0, Integer.MAX_VALUE);
  }
 
  @SuppressWarnings("rawtypes")
  public List selectList(String statement, Object parameter, Page page) {  
    if (page!=null) {
      return selectList(statement, parameter, page.getFirstResult(), page.getMaxResults());
    } else {
      return selectList(statement, parameter, 0, Integer.MAX_VALUE);
    }
  }
 
  @SuppressWarnings("rawtypes")
  public List selectList(String statement, ListQueryParameterObject parameter, Page page) {  
    return selectList(statement, parameter);
  }

  @SuppressWarnings("rawtypes")
  public List selectList(String statement, Object parameter, int firstResult, int maxResults) {  
    return selectList(statement, new ListQueryParameterObject(parameter, firstResult, maxResults));
  }
 
  @SuppressWarnings("rawtypes")
  public List selectList(String statement, ListQueryParameterObject parameter) {
    return selectListWithRawParameter(statement, parameter, parameter.getFirstResult(), parameter.getMaxResults());
  }
 
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public List selectListWithRawParameter(String statement, Object parameter, int firstResult, int maxResults) {
    statement = dbSqlSessionFactory.mapStatement(statement);   
    if (firstResult == -1 ||  maxResults == -1) {
      return Collections.EMPTY_LIST;
    }   
    List loadedObjects = sqlSession.selectList(statement, parameter);
    return filterLoadedObjects(loadedObjects);
  }
 
  @SuppressWarnings({ "rawtypes" })
  public List selectListWithRawParameterWithoutFilter(String statement, Object parameter, int firstResult, int maxResults) {
    statement = dbSqlSessionFactory.mapStatement(statement);   
    if (firstResult == -1 ||  maxResults == -1) {
      return Collections.EMPTY_LIST;
    }   
    return sqlSession.selectList(statement, parameter);
  }

  public Object selectOne(String statement, Object parameter) {
    statement = dbSqlSessionFactory.mapStatement(statement);
    Object result = sqlSession.selectOne(statement, parameter);
    if (result instanceof PersistentObject) {
      PersistentObject loadedObject = (PersistentObject) result;
      result = cacheFilter(loadedObject);
    }
    return result;
  }
 
  @SuppressWarnings("unchecked")
  public <T extends PersistentObject> T selectById(Class<T> entityClass, String id) {
    T persistentObject = cacheGet(entityClass, id);
    if (persistentObject!=null) {
      return persistentObject;
    }
    String selectStatement = dbSqlSessionFactory.getSelectStatement(entityClass);
    selectStatement = dbSqlSessionFactory.mapStatement(selectStatement);
    persistentObject = (T) sqlSession.selectOne(selectStatement, id);
    if (persistentObject==null) {
      return null;
    }
    cachePut(persistentObject, true);
    return persistentObject;
  }

  // internal session cache ///////////////////////////////////////////////////
 
  @SuppressWarnings("rawtypes")
  protected List filterLoadedObjects(List<Object> loadedObjects) {
    if (loadedObjects.isEmpty()) {
      return loadedObjects;
    }
    if (!(loadedObjects.get(0) instanceof PersistentObject)) {
      return loadedObjects;
    }
   
    List<PersistentObject> filteredObjects = new ArrayList<PersistentObject>(loadedObjects.size());
    for (Object loadedObject: loadedObjects) {
      PersistentObject cachedPersistentObject = cacheFilter((PersistentObject) loadedObject);
      filteredObjects.add(cachedPersistentObject);
    }
    return filteredObjects;
  }

  protected CachedObject cachePut(PersistentObject persistentObject, boolean storeState) {
    Map<String, CachedObject> classCache = cachedObjects.get(persistentObject.getClass());
    if (classCache==null) {
      classCache = new HashMap<String, CachedObject>();
      cachedObjects.put(persistentObject.getClass(), classCache);
    }
    CachedObject cachedObject = new CachedObject(persistentObject, storeState);
    classCache.put(persistentObject.getId(), cachedObject);
    return cachedObject;
  }
 
  /** returns the object in the cache.  if this object was loaded before,
   * then the original object is returned.  if this is the first time
   * this object is loaded, then the loadedObject is added to the cache. */
  protected PersistentObject cacheFilter(PersistentObject persistentObject) {
    PersistentObject cachedPersistentObject = cacheGet(persistentObject.getClass(), persistentObject.getId());
    if (cachedPersistentObject!=null) {
      return cachedPersistentObject;
    }
    cachePut(persistentObject, true);
    return persistentObject;
  }

  @SuppressWarnings("unchecked")
  protected <T> T cacheGet(Class<T> entityClass, String id) {
    CachedObject cachedObject = null;
    Map<String, CachedObject> classCache = cachedObjects.get(entityClass);
    if (classCache!=null) {
      cachedObject = classCache.get(id);
    }
    if (cachedObject!=null) {
      return (T) cachedObject.getPersistentObject();
    }
    return null;
  }
 
  protected void cacheRemove(Class<?> persistentObjectClass, String persistentObjectId) {
    Map<String, CachedObject> classCache = cachedObjects.get(persistentObjectClass);
    if (classCache==null) {
      return;
    }
    classCache.remove(persistentObjectId);
  }
 
  @SuppressWarnings("unchecked")
  public <T> List<T> findInCache(Class<T> entityClass) {
    Map<String, CachedObject> classCache = cachedObjects.get(entityClass);
    if (classCache!=null) {
      List<T> entities = new ArrayList<T>(classCache.size());
      for (CachedObject cachedObject: classCache.values()) {
        entities.add((T) cachedObject.getPersistentObject());
      }
      return entities;
    }
    return Collections.emptyList();
  }
 
  public <T> T findInCache(Class<T> entityClass, String id) {
    return cacheGet(entityClass, id);
  }
 
  public static class CachedObject {
    protected PersistentObject persistentObject;
    protected Object persistentObjectState;
   
    public CachedObject(PersistentObject persistentObject, boolean storeState) {
      this.persistentObject = persistentObject;
      if (storeState) {
        this.persistentObjectState = persistentObject.getPersistentState();
      }
    }

    public PersistentObject getPersistentObject() {
      return persistentObject;
    }

    public Object getPersistentObjectState() {
      return persistentObjectState;
    }
  }

  // deserialized objects /////////////////////////////////////////////////////
 
  public void addDeserializedObject(DeserializedObject deserializedObject) {
    deserializedObjects.add(deserializedObject);
  }

  // flush ////////////////////////////////////////////////////////////////////

  public void flush() {
    List<DeleteOperation> removedOperations = removeUnnecessaryOperations();
   
    flushDeserializedObjects();
    List<PersistentObject> updatedObjects = getUpdatedObjects();
   
    if (log.isDebugEnabled()) {
      log.debug("flush summary: {} insert, {} update, {} delete.", insertedObjects.size(), updatedObjects.size(), deleteOperations.size());
      for (PersistentObject insertedObject: insertedObjects) {
        log.debug("  insert {}", insertedObject);
      }
      for (PersistentObject updatedObject: updatedObjects) {
        log.debug("  update {}", updatedObject);
      }
      for (DeleteOperation deleteOperation: deleteOperations) {
        log.debug("  {}", deleteOperation);
      }
      log.debug("now executing flush...");
    }

    flushInserts();
    flushUpdates(updatedObjects);
    flushDeletes(removedOperations);
  }

  /**
   * Clears all deleted and inserted objects from the cache,
   * and removes inserts and deletes that cancel each other.
   */
  protected List<DeleteOperation> removeUnnecessaryOperations() {
    List<DeleteOperation> removedDeleteOperations = new ArrayList<DeleteOperation>();

    for (Iterator<DeleteOperation> deleteIt = deleteOperations.iterator(); deleteIt.hasNext();) {
      DeleteOperation deleteOperation = deleteIt.next();
     
      for (Iterator<PersistentObject> insertIt = insertedObjects.iterator(); insertIt.hasNext();) {
        PersistentObject insertedObject = insertIt.next();
       
        // if the deleted object is inserted,
        if (deleteOperation.sameIdentity(insertedObject)) {
          // remove the insert and the delete, they cancel each other
          insertIt.remove();
          deleteIt.remove();
          // add removed operations to be able to fire events
          removedDeleteOperations.add( deleteOperation);
        }
      }
     
      // in any case, remove the deleted object from the cache
      deleteOperation.clearCache();
    }
   
    for (PersistentObject insertedObject: insertedObjects) {
      cacheRemove(insertedObject.getClass(), insertedObject.getId());
    }

    return removedDeleteOperations;
  }
 
  /**
   * Optimizes the given delete operations:
   * for example, if there are two deletes for two different variables, merges this into
   * one bulk delete which improves performance
   */
  protected List<DeleteOperation> optimizeDeleteOperations(List<DeleteOperation> deleteOperations) {
   
    // No optimization possible for 0 or 1 operations
    if (!isOptimizeDeleteOperationsEnabled || deleteOperations.size() <= 1) {
      return deleteOperations;
    }
   
    List<DeleteOperation> optimizedDeleteOperations = new ArrayList<DbSqlSession.DeleteOperation>();
    boolean[] checkedIndices = new boolean[deleteOperations.size()];
    for (int i=0; i<deleteOperations.size(); i++) {
     
      if (checkedIndices[i] == true) {
        continue;
      }
     
      DeleteOperation deleteOperation = deleteOperations.get(i);
      boolean couldOptimize = false;
      if (deleteOperation instanceof CheckedDeleteOperation) {
       
        PersistentObject persistentObject = ((CheckedDeleteOperation) deleteOperation).getPersistentObject();
        if (persistentObject instanceof BulkDeleteable) {
        String bulkDeleteStatement = dbSqlSessionFactory.getBulkDeleteStatement(persistentObject.getClass());
        bulkDeleteStatement = dbSqlSessionFactory.mapStatement(bulkDeleteStatement);
        if (bulkDeleteStatement != null) {
          BulkCheckedDeleteOperation bulkCheckedDeleteOperation = null;
         
          // Find all objects of the same type
          for (int j=0; j<deleteOperations.size(); j++) {
            DeleteOperation otherDeleteOperation = deleteOperations.get(j);
            if (j != i && checkedIndices[j] == false && otherDeleteOperation instanceof CheckedDeleteOperation) {
              PersistentObject otherPersistentObject = ((CheckedDeleteOperation) otherDeleteOperation).getPersistentObject();
              if (otherPersistentObject.getClass().equals(persistentObject.getClass())) {
                  if (bulkCheckedDeleteOperation == null) {
                    bulkCheckedDeleteOperation = new BulkCheckedDeleteOperation(persistentObject.getClass());
                    bulkCheckedDeleteOperation.addPersistentObject(persistentObject);
                    optimizedDeleteOperations.add(bulkCheckedDeleteOperation);
                  }
                  couldOptimize = true;
                  bulkCheckedDeleteOperation.addPersistentObject(otherPersistentObject);
                  checkedIndices[j] = true;
              } else {
                  // We may only optimize subsequent delete operations of the same type, to prevent messing up
                  // the order of deletes of related entities which may depend on the referenced entity being deleted before
                  break;
              }
            }
           
          }
        }
        }
      }
     
       if (!couldOptimize) {
        optimizedDeleteOperations.add(deleteOperation);
      }
      checkedIndices[i]=true;
     
    }
    return optimizedDeleteOperations;
  }

  protected void flushDeserializedObjects() {
    for (DeserializedObject deserializedObject: deserializedObjects) {
      deserializedObject.flush();
    }
  }

  public List<PersistentObject> getUpdatedObjects() {
    List<PersistentObject> updatedObjects = new ArrayList<PersistentObject>();
    for (Class<?> clazz: cachedObjects.keySet()) {
     
      Map<String, CachedObject> classCache = cachedObjects.get(clazz);
      for (CachedObject cachedObject: classCache.values()) {
       
        PersistentObject persistentObject = cachedObject.getPersistentObject();
        if (!isPersistentObjectDeleted(persistentObject)) {
          Object originalState = cachedObject.getPersistentObjectState();
          if (persistentObject.getPersistentState() != null &&
              !persistentObject.getPersistentState().equals(originalState)) {
            updatedObjects.add(persistentObject);
          } else {
            log.trace("loaded object '{}' was not updated", persistentObject);
          }
        }
       
      }
     
    }
    return updatedObjects;
  }
 
  protected boolean isPersistentObjectDeleted(PersistentObject persistentObject) {
    for (DeleteOperation deleteOperation : deleteOperations) {
      if (deleteOperation.sameIdentity(persistentObject)) {
        return true;
      }
    }
    return false;
  }
 
  public <T extends PersistentObject> List<T> pruneDeletedEntities(List<T> listToPrune) {  
    List<T> prunedList = new ArrayList<T>(listToPrune);
    for (T potentiallyDeleted : listToPrune) {
      for (DeleteOperation deleteOperation: deleteOperations) {
         
        if (deleteOperation.sameIdentity(potentiallyDeleted)) {
          prunedList.remove(potentiallyDeleted);
        }
         
      }
    }
    return prunedList;
  }

  protected void flushInserts() {
    for (PersistentObject insertedObject: insertedObjects) {
      String insertStatement = dbSqlSessionFactory.getInsertStatement(insertedObject);
      insertStatement = dbSqlSessionFactory.mapStatement(insertStatement);

      if (insertStatement==null) {
        throw new ActivitiException("no insert statement for "+insertedObject.getClass()+" in the ibatis mapping files");
      }
     
      log.debug("inserting: {}", insertedObject);
      sqlSession.insert(insertStatement, insertedObject);
     
      // See http://jira.codehaus.org/browse/ACT-1290
      if (insertedObject instanceof HasRevision) {
        ((HasRevision) insertedObject).setRevision(((HasRevision) insertedObject).getRevisionNext());
      }
    }
    insertedObjects.clear();
  }

  protected void flushUpdates(List<PersistentObject> updatedObjects) {
    for (PersistentObject updatedObject: updatedObjects) {
      String updateStatement = dbSqlSessionFactory.getUpdateStatement(updatedObject);
      updateStatement = dbSqlSessionFactory.mapStatement(updateStatement);
     
      if (updateStatement==null) {
        throw new ActivitiException("no update statement for "+updatedObject.getClass()+" in the ibatis mapping files");
      }
     
      log.debug("updating: {}", updatedObject);
      int updatedRecords = sqlSession.update(updateStatement, updatedObject);
      if (updatedRecords!=1) {
        throw new ActivitiOptimisticLockingException(updatedObject + " was updated by another transaction concurrently");
      }
     
      // See http://jira.codehaus.org/browse/ACT-1290
      if (updatedObject instanceof HasRevision) {
        ((HasRevision) updatedObject).setRevision(((HasRevision) updatedObject).getRevisionNext());
      }
     
    }
    updatedObjects.clear();
  }

  protected void flushDeletes(List<DeleteOperation> removedOperations) {
    boolean dispatchEvent = Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled();

    flushRegularDeletes(dispatchEvent);

    if (dispatchEvent) {
      dispatchEventsForRemovedOperations(removedOperations);
    }

    deleteOperations.clear();
  }

  protected void dispatchEventsForRemovedOperations(List<DeleteOperation> removedOperations) {
    for (DeleteOperation delete : removedOperations) {
      // dispatch removed delete events
      if (delete instanceof CheckedDeleteOperation) {
        CheckedDeleteOperation checkedDeleteOperation = (CheckedDeleteOperation) delete;
        PersistentObject persistentObject = checkedDeleteOperation.getPersistentObject();
        if (persistentObject instanceof VariableInstanceEntity) {
          VariableInstanceEntity variableInstance = (VariableInstanceEntity) persistentObject;
          Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
            createVariableDeleteEvent(variableInstance)
          );
        }
      }
    }
  }

  protected static ActivitiVariableEvent createVariableDeleteEvent(VariableInstanceEntity variableInstance) {
    return ActivitiEventBuilder.createVariableEvent(ActivitiEventType.VARIABLE_DELETED, variableInstance.getName(), null, variableInstance.getType(),
        variableInstance.getTaskId(), variableInstance.getExecutionId(), variableInstance.getProcessInstanceId(), null);
  }

  protected void flushRegularDeletes(boolean dispatchEvent) {
    List<DeleteOperation> optimizedDeleteOperations = optimizeDeleteOperations(deleteOperations);
    for (DeleteOperation delete : optimizedDeleteOperations) {
//    for (DeleteOperation delete : deleteOperations) {
      log.debug("executing: {}", delete);

      delete.execute();

      //  fire event for variable delete operation. (BulkDeleteOperation is not taken into account)
      if (dispatchEvent) {
        //  prepare delete event to fire for variable delete operation. (BulkDeleteOperation is not taken into account)
        if (delete instanceof CheckedDeleteOperation) {
          CheckedDeleteOperation checkedDeleteOperation = (CheckedDeleteOperation) delete;
          PersistentObject persistentObject = checkedDeleteOperation.getPersistentObject();
          if (persistentObject instanceof VariableInstanceEntity) {
            VariableInstanceEntity variableInstance = (VariableInstanceEntity) persistentObject;
            Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
              createVariableDeleteEvent(variableInstance)
            );
          }
        } else if (delete instanceof BulkCheckedDeleteOperation) {
          BulkCheckedDeleteOperation bulkCheckedDeleteOperation = (BulkCheckedDeleteOperation) delete;
          if (VariableInstanceEntity.class.isAssignableFrom(bulkCheckedDeleteOperation.getPersistentObjectClass())) {
            for (PersistentObject persistentObject : bulkCheckedDeleteOperation.getPersistentObjects()) {
               VariableInstanceEntity variableInstance = (VariableInstanceEntity) persistentObject;
               Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                 createVariableDeleteEvent(variableInstance)
               );
            }
          }
        }
      }
    }
  }

  public void close() {
    sqlSession.close();
  }

  public void commit() {
    sqlSession.commit();
  }

  public void rollback() {
    sqlSession.rollback();
  }
 
  // schema operations ////////////////////////////////////////////////////////
 
  public void dbSchemaCheckVersion() {
    try {
      String dbVersion = getDbVersion();
      if (!ProcessEngine.VERSION.equals(dbVersion)) {
        throw new ActivitiWrongDbException(ProcessEngine.VERSION, dbVersion);
      }

      String errorMessage = null;
      if (!isEngineTablePresent()) {
        errorMessage = addMissingComponent(errorMessage, "engine");
      }
      if (dbSqlSessionFactory.isDbHistoryUsed() && !isHistoryTablePresent()) {
        errorMessage = addMissingComponent(errorMessage, "history");
      }
      if (dbSqlSessionFactory.isDbIdentityUsed() && !isIdentityTablePresent()) {
        errorMessage = addMissingComponent(errorMessage, "identity");
      }
     
      if (errorMessage!=null) {
        throw new ActivitiException("Activiti database problem: "+errorMessage);
      }
     
    } catch (Exception e) {
      if (isMissingTablesException(e)) {
        throw new ActivitiException("no activiti tables in db. set <property name=\"databaseSchemaUpdate\" to value=\"true\" or value=\"create-drop\" (use create-drop for testing only!) in bean processEngineConfiguration in activiti.cfg.xml for automatic schema creation", e);
      } else {
        if (e instanceof RuntimeException) {
          throw (RuntimeException) e;
        } else {
          throw new ActivitiException("couldn't get db schema version", e);
        }
      }
    }

    log.debug("activiti db schema check successful");
  }

  protected String addMissingComponent(String missingComponents, String component) {
    if (missingComponents==null) {
      return "Tables missing for component(s) "+component;
    }
    return missingComponents+", "+component;
  }

  protected String getDbVersion() {
    String selectSchemaVersionStatement = dbSqlSessionFactory.mapStatement("selectDbSchemaVersion");
    return (String) sqlSession.selectOne(selectSchemaVersionStatement);
  }

  public void dbSchemaCreate() {
    ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
   
    if (isEngineTablePresent()) {
      String dbVersion = getDbVersion();
      if (!ProcessEngine.VERSION.equals(dbVersion)) {
        throw new ActivitiWrongDbException(ProcessEngine.VERSION, dbVersion);
      }
    } else {
      dbSchemaCreateEngine();
    }

    if (processEngineConfiguration.getHistoryLevel() != HistoryLevel.NONE) {
      dbSchemaCreateHistory();
    }

    if (processEngineConfiguration.isDbIdentityUsed()) {
      dbSchemaCreateIdentity();
    }
  }

  protected void dbSchemaCreateIdentity() {
    executeMandatorySchemaResource("create", "identity");
  }

  protected void dbSchemaCreateHistory() {
    executeMandatorySchemaResource("create", "history");
  }

  protected void dbSchemaCreateEngine() {
    executeMandatorySchemaResource("create", "engine");
  }

  public void dbSchemaDrop() {
    executeMandatorySchemaResource("drop", "engine");
    if (dbSqlSessionFactory.isDbHistoryUsed()) {
      executeMandatorySchemaResource("drop", "history");
    }
    if (dbSqlSessionFactory.isDbIdentityUsed()) {
      executeMandatorySchemaResource("drop", "identity");
    }
  }

  public void dbSchemaPrune() {
    if (isHistoryTablePresent() && !dbSqlSessionFactory.isDbHistoryUsed()) {
      executeMandatorySchemaResource("drop", "history");
    }
    if (isIdentityTablePresent() && dbSqlSessionFactory.isDbIdentityUsed()) {
      executeMandatorySchemaResource("drop", "identity");
    }
  }

  public void executeMandatorySchemaResource(String operation, String component) {
    executeSchemaResource(operation, component, getResourceForDbOperation(operation, operation, component), false);
  }

  public static String[] JDBC_METADATA_TABLE_TYPES = {"TABLE"};

  public String dbSchemaUpdate() {

    String feedback = null;
    boolean isUpgradeNeeded = false;
    int matchingVersionIndex = -1;

    if (isEngineTablePresent()) {

      PropertyEntity dbVersionProperty = selectById(PropertyEntity.class,"schema.version");
      String dbVersion = dbVersionProperty.getValue();

      // Determine index in the sequence of Activiti releases
      int index = 0;
      while (matchingVersionIndex < 0 && index < ACTIVITI_VERSIONS.size()) {
        if (ACTIVITI_VERSIONS.get(index).matches(dbVersion)) {
          matchingVersionIndex = index;
        } else {
          index++;
        }
      }

      // Exception when no match was found: unknown/unsupported version
      if (matchingVersionIndex < 0) {
        throw new ActivitiException(
            "Could not update Activiti database schema: unknown version from database: '"
                + dbVersion + "'");
      }

      isUpgradeNeeded = (matchingVersionIndex != (ACTIVITI_VERSIONS.size() - 1));

      if (isUpgradeNeeded) {
        dbVersionProperty.setValue(ProcessEngine.VERSION);

        PropertyEntity dbHistoryProperty;
        if ("5.0".equals(dbVersion)) {
          dbHistoryProperty = new PropertyEntity("schema.history", "create(5.0)");
          insert(dbHistoryProperty);
        } else {
          dbHistoryProperty = selectById(PropertyEntity.class, "schema.history");
        }

        // Set upgrade history
        String dbHistoryValue = dbHistoryProperty.getValue() + " upgrade(" + dbVersion + "->" + ProcessEngine.VERSION + ")";
        dbHistoryProperty.setValue(dbHistoryValue);

        // Engine upgrade
        dbSchemaUpgrade("engine", matchingVersionIndex);
        feedback = "upgraded Activiti from " + dbVersion + " to "+ ProcessEngine.VERSION;
      }

    } else {
      dbSchemaCreateEngine();
    }
    if (isHistoryTablePresent()) {
      if (isUpgradeNeeded) {
        dbSchemaUpgrade("history", matchingVersionIndex);
      }
    } else if (dbSqlSessionFactory.isDbHistoryUsed()) {
      dbSchemaCreateHistory();
    }
   
    if (isIdentityTablePresent()) {
      if (isUpgradeNeeded) {
        dbSchemaUpgrade("identity", matchingVersionIndex);
      }
    } else if (dbSqlSessionFactory.isDbIdentityUsed()) {
      dbSchemaCreateIdentity();
    }
   
    return feedback;
  }

  public boolean isEngineTablePresent(){
    return isTablePresent("ACT_RU_EXECUTION");
  }
  public boolean isHistoryTablePresent(){
    return isTablePresent("ACT_HI_PROCINST");
  }
  public boolean isIdentityTablePresent(){
    return isTablePresent("ACT_ID_USER");
  }

  public boolean isTablePresent(String tableName) {
    // ACT-1610: in case the prefix IS the schema itself, we don't add the prefix, since the
    // check is already aware of the schema
    if (!dbSqlSessionFactory.isTablePrefixIsSchema()) {
      tableName = prependDatabaseTablePrefix(tableName);
    }
   
    Connection connection = null;
    try {
      connection = sqlSession.getConnection();
      DatabaseMetaData databaseMetaData = connection.getMetaData();
      ResultSet tables = null;

      String catalog = this.connectionMetadataDefaultCatalog;
      if (dbSqlSessionFactory.getDatabaseCatalog() != null && dbSqlSessionFactory.getDatabaseCatalog().length() > 0) {
        catalog = dbSqlSessionFactory.getDatabaseCatalog();
      }

      String schema = this.connectionMetadataDefaultSchema;
      if (dbSqlSessionFactory.getDatabaseSchema() != null && dbSqlSessionFactory.getDatabaseSchema().length() > 0) {
        schema = dbSqlSessionFactory.getDatabaseSchema();
      }
     
      String databaseType = dbSqlSessionFactory.getDatabaseType();
     
      if ("postgres".equals(databaseType)) {
        tableName = tableName.toLowerCase();
      }
     
      try {
        tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);
        return tables.next();
      } finally {
        try {
          tables.close();
        } catch (Exception e) {
          log.error("Error closing meta data tables", e);
        }
      }
     
    } catch (Exception e) {
      throw new ActivitiException("couldn't check if tables are already present using metadata: "+e.getMessage(), e);
    }
  }
 
  protected boolean isUpgradeNeeded(String versionInDatabase) {
    if(ProcessEngine.VERSION.equals(versionInDatabase)) {
      return false;
    }
   
    String cleanDbVersion = getCleanVersion(versionInDatabase);
    String[] cleanDbVersionSplitted = cleanDbVersion.split("\\.");
    int dbMajorVersion = Integer.valueOf(cleanDbVersionSplitted[0]);
    int dbMinorVersion = Integer.valueOf(cleanDbVersionSplitted[1]);
   
    String cleanEngineVersion = getCleanVersion(ProcessEngine.VERSION);
    String[] cleanEngineVersionSplitted = cleanEngineVersion.split("\\.");
    int engineMajorVersion = Integer.valueOf(cleanEngineVersionSplitted[0]);
    int engineMinorVersion = Integer.valueOf(cleanEngineVersionSplitted[1]);
     
    if((dbMajorVersion > engineMajorVersion)
            || ( (dbMajorVersion <= engineMajorVersion) && (dbMinorVersion > engineMinorVersion) )) {
      throw new ActivitiException("Version of activiti database (" + versionInDatabase + ") is more recent than the engine (" + ProcessEngine.VERSION +")");
    } else if(cleanDbVersion.compareTo(cleanEngineVersion) == 0) {
      // Versions don't match exactly, possibly snapshot is being used
      log.warn("Engine-version is the same, but not an exact match: {} vs. {}. Not performing database-upgrade.", versionInDatabase, ProcessEngine.VERSION);
      return false;
    }
    return true;
  }
 
  protected String getCleanVersion(String versionString) {
    Matcher matcher = CLEAN_VERSION_REGEX.matcher(versionString);
    if(!matcher.find()) {
      throw new ActivitiException("Illegal format for version: " + versionString);
    }
   
    String cleanString = matcher.group();
    try {
      Double.parseDouble(cleanString); // try to parse it, to see if it is really a number
      return cleanString;
    } catch(NumberFormatException nfe) {
      throw new ActivitiException("Illegal format for version: " + versionString);
    }
  }
 
  protected String prependDatabaseTablePrefix(String tableName) {
    return dbSqlSessionFactory.getDatabaseTablePrefix() + tableName;   
  }
 
  protected void dbSchemaUpgrade(final String component, final int currentDatabaseVersionsIndex) {
    ActivitiVersion activitiVersion = ACTIVITI_VERSIONS.get(currentDatabaseVersionsIndex);
    String dbVersion = activitiVersion.getMainVersion();
    log.info("upgrading activiti {} schema from {} to {}", component, dbVersion, ProcessEngine.VERSION);
   
    // Actual execution of schema DDL SQL
    for (int i=currentDatabaseVersionsIndex + 1; i<ACTIVITI_VERSIONS.size(); i++) {
      String nextVersion = ACTIVITI_VERSIONS.get(i).getMainVersion();
     
      // Taking care of -SNAPSHOT version in development
      if (nextVersion.endsWith("-SNAPSHOT")) {
        nextVersion = nextVersion.substring(0, nextVersion.length()-"-SNAPSHOT".length());
      }
     
      dbVersion = dbVersion.replace(".", "");
      nextVersion = nextVersion.replace(".", "");
      log.info("Upgrade needed: {} -> {}. Looking for schema update resource for component '{}'", dbVersion, nextVersion, component);
      executeSchemaResource("upgrade", component, getResourceForDbOperation("upgrade", "upgradestep." + dbVersion + ".to." + nextVersion, component), true);
      dbVersion = nextVersion;
    }
  }
 
  public String getResourceForDbOperation(String directory, String operation, String component) {
    String databaseType = dbSqlSessionFactory.getDatabaseType();
    return "org/activiti/db/" + directory + "/activiti." + databaseType + "." + operation + "."+component+".sql";
  }

  public void executeSchemaResource(String operation, String component, String resourceName, boolean isOptional) {
    InputStream inputStream = null;
    try {
      inputStream = ReflectUtil.getResourceAsStream(resourceName);
      if (inputStream == null) {
        if (isOptional) {
          log.info("no schema resource {} for {}", resourceName, operation);
        } else {
          throw new ActivitiException("resource '" + resourceName + "' is not available");
        }
      } else {
        executeSchemaResource(operation, component, resourceName, inputStream);
      }

    } finally {
      IoUtil.closeSilently(inputStream);
    }
  }

  private void executeSchemaResource(String operation, String component, String resourceName, InputStream inputStream) {
    log.info("performing {} on {} with resource {}", operation, component, resourceName);
    String sqlStatement = null;
    String exceptionSqlStatement = null;
    try {
      Connection connection = sqlSession.getConnection();
      Exception exception = null;
      byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
      String ddlStatements = new String(bytes);
      String databaseType = dbSqlSessionFactory.getDatabaseType();
     
      // Special DDL handling for certain databases
      try {
        if ("mysql".equals(databaseType)) {
          DatabaseMetaData databaseMetaData = connection.getMetaData();
          int majorVersion = databaseMetaData.getDatabaseMajorVersion();
          int minorVersion = databaseMetaData.getDatabaseMinorVersion();
          log.info("Found MySQL: majorVersion=" + majorVersion + " minorVersion=" + minorVersion);
         
          // Special care for MySQL < 5.6
          if (majorVersion <= 5 && minorVersion < 6) {
            ddlStatements = updateDdlForMySqlVersionLowerThan56(ddlStatements);
          }
        }
      } catch (Exception e) {
        log.info("Could not get database metadata", e);
      }
     
      BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));
      String line = readNextTrimmedLine(reader);
      boolean inOraclePlsqlBlock = false;
      while (line != null) {
        if (line.startsWith("# ")) {
          log.debug(line.substring(2));
         
        } else if (line.startsWith("-- ")) {
          log.debug(line.substring(3));
         
        } else if (line.startsWith("execute java ")) {
          String upgradestepClassName = line.substring(13).trim();
          DbUpgradeStep dbUpgradeStep = null;
          try {
            dbUpgradeStep = (DbUpgradeStep) ReflectUtil.instantiate(upgradestepClassName);
          } catch (ActivitiException e) {
            throw new ActivitiException("database update java class '"+upgradestepClassName+"' can't be instantiated: "+e.getMessage(), e);
          }
          try {
            log.debug("executing upgrade step java class {}", upgradestepClassName);
            dbUpgradeStep.execute(this);
          } catch (Exception e) {
            throw new ActivitiException("error while executing database update java class '"+upgradestepClassName+"': "+e.getMessage(), e);
          }
         
        } else if (line.length()>0) {
         
          if ("oracle".equals(databaseType) && line.startsWith("begin")) {
            inOraclePlsqlBlock = true;
            sqlStatement = addSqlStatementPiece(sqlStatement, line);
           
          } else if ((line.endsWith(";") && inOraclePlsqlBlock == false) ||
              (line.startsWith("/") && inOraclePlsqlBlock == true)) {
           
            if (inOraclePlsqlBlock) {
              inOraclePlsqlBlock = false;
            } else {
              sqlStatement = addSqlStatementPiece(sqlStatement, line.substring(0, line.length()-1));
            }
           
            Statement jdbcStatement = connection.createStatement();
            try {
              // no logging needed as the connection will log it
              log.debug("SQL: {}", sqlStatement);
              jdbcStatement.execute(sqlStatement);
              jdbcStatement.close();
            } catch (Exception e) {
              if (exception == null) {
                exception = e;
                exceptionSqlStatement = sqlStatement;
              }
              log.error("problem during schema {}, statement {}", operation, sqlStatement, e);
            } finally {
              sqlStatement = null;
            }
          } else {
            sqlStatement = addSqlStatementPiece(sqlStatement, line);
          }
        }
       
        line = readNextTrimmedLine(reader);
      }

      if (exception != null) {
        throw exception;
      }
     
      log.debug("activiti db schema {} for component {} successful", operation, component);
     
    } catch (Exception e) {
      throw new ActivitiException("couldn't "+operation+" db schema: "+exceptionSqlStatement, e);
    }
  }
 
  /**
   * MySQL is funny when it comes to timestamps and dates.
   * 
   * More specifically, for a DDL statement like 'MYCOLUMN timestamp(3)':
   *   - MySQL 5.6.4+ has support for timestamps/dates with millisecond (or smaller) precision.
   *     The DDL above works and the data in the table will have millisecond precision
   *   - MySQL < 5.5.3 allows the DDL statement, but ignores it.
   *     The DDL above works but the data won't have millisecond precision
   *   - MySQL 5.5.3 < [version] < 5.6.4 gives and exception when using the DDL above.
   *  
   * Also, the 5.5 and 5.6 branches of MySQL are both actively developed and patched.
   *
   * Hence, when doing auto-upgrade/creation of the Activiti tables, the default
   * MySQL DDL file is used and all timestamps/datetimes are converted to not use the
   * millisecond precision by string replacement done in the method below.
   *
   * If using the DDL files directly (which is a sane choice in production env.),
   * there is a distinction between MySQL version < 5.6.
   */
  protected String updateDdlForMySqlVersionLowerThan56(String ddlStatements) {
    return ddlStatements.replace("timestamp(3)", "timestamp")
                .replace("datetime(3)", "datetime")
                .replace("TIMESTAMP(3)", "TIMESTAMP")
                .replace("DATETIME(3)", "DATETIME");
  }

  protected String addSqlStatementPiece(String sqlStatement, String line) {
    if (sqlStatement==null) {
      return line;
    }
    return sqlStatement + " \n" + line;
  }
 
  protected String readNextTrimmedLine(BufferedReader reader) throws IOException {
    String line = reader.readLine();
    if (line!=null) {
      line = line.trim();
    }
    return line;
  }
 
  protected boolean isMissingTablesException(Exception e) {
    String exceptionMessage = e.getMessage();
    if(e.getMessage() != null) {     
      // Matches message returned from H2
      if ((exceptionMessage.indexOf("Table") != -1) && (exceptionMessage.indexOf("not found") != -1)) {
        return true;
      }
     
      // Message returned from MySQL and Oracle
      if (((exceptionMessage.indexOf("Table") != -1 || exceptionMessage.indexOf("table") != -1)) && (exceptionMessage.indexOf("doesn't exist") != -1)) {
        return true;
      }
     
      // Message returned from Postgres
      if (((exceptionMessage.indexOf("relation") != -1 || exceptionMessage.indexOf("table") != -1)) && (exceptionMessage.indexOf("does not exist") != -1)) {
        return true;
      }
    }
    return false;
  }
 
  public void performSchemaOperationsProcessEngineBuild() {
    String databaseSchemaUpdate = Context.getProcessEngineConfiguration().getDatabaseSchemaUpdate();
    if (ProcessEngineConfigurationImpl.DB_SCHEMA_UPDATE_DROP_CREATE.equals(databaseSchemaUpdate)) {
      try {
        dbSchemaDrop();
      } catch (RuntimeException e) {
        // ignore
      }
    }
    if ( org.activiti.engine.ProcessEngineConfiguration.DB_SCHEMA_UPDATE_CREATE_DROP.equals(databaseSchemaUpdate)
         || ProcessEngineConfigurationImpl.DB_SCHEMA_UPDATE_DROP_CREATE.equals(databaseSchemaUpdate)
         || ProcessEngineConfigurationImpl.DB_SCHEMA_UPDATE_CREATE.equals(databaseSchemaUpdate)
       ) {
      dbSchemaCreate();
     
    } else if (org.activiti.engine.ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE.equals(databaseSchemaUpdate)) {
      dbSchemaCheckVersion();
     
    } else if (ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE.equals(databaseSchemaUpdate)) {
      dbSchemaUpdate();
    }
  }

  public void performSchemaOperationsProcessEngineClose() {
    String databaseSchemaUpdate = Context.getProcessEngineConfiguration().getDatabaseSchemaUpdate();
    if (org.activiti.engine.ProcessEngineConfiguration.DB_SCHEMA_UPDATE_CREATE_DROP.equals(databaseSchemaUpdate)) {
      dbSchemaDrop();
    }
  }
 
  public <T> T getCustomMapper(Class<T> type) {
    return sqlSession.getMapper(type);
  }

  // query factory methods //////////////////////////////////////////////////// 

  public DeploymentQueryImpl createDeploymentQuery() {
    return new DeploymentQueryImpl();
  }
  public ModelQueryImpl createModelQueryImpl() {
    return new ModelQueryImpl();
  }
  public ProcessDefinitionQueryImpl createProcessDefinitionQuery() {
    return new ProcessDefinitionQueryImpl();
  }
  public ProcessInstanceQueryImpl createProcessInstanceQuery() {
    return new ProcessInstanceQueryImpl();
  }
  public ExecutionQueryImpl createExecutionQuery() {
    return new ExecutionQueryImpl();
  }
  public TaskQueryImpl createTaskQuery() {
    return new TaskQueryImpl();
  }
  public JobQueryImpl createJobQuery() {
    return new JobQueryImpl();
  }
  public HistoricProcessInstanceQueryImpl createHistoricProcessInstanceQuery() {
    return new HistoricProcessInstanceQueryImpl();
  }
  public HistoricActivityInstanceQueryImpl createHistoricActivityInstanceQuery() {
    return new HistoricActivityInstanceQueryImpl();
  }
  public HistoricTaskInstanceQueryImpl createHistoricTaskInstanceQuery() {
    return new HistoricTaskInstanceQueryImpl();
  }
  public HistoricDetailQueryImpl createHistoricDetailQuery() {
    return new HistoricDetailQueryImpl();
  }
  public HistoricVariableInstanceQueryImpl createHistoricVariableInstanceQuery() {
    return new HistoricVariableInstanceQueryImpl();
  }
  public UserQueryImpl createUserQuery() {
    return new UserQueryImpl();
  }
  public GroupQueryImpl createGroupQuery() {
    return new GroupQueryImpl();
  }

  // getters and setters //////////////////////////////////////////////////////
 
  public SqlSession getSqlSession() {
    return sqlSession;
  }
  public DbSqlSessionFactory getDbSqlSessionFactory() {
    return dbSqlSessionFactory;
  }

  public boolean isOptimizeDeleteOperationsEnabled() {
    return isOptimizeDeleteOperationsEnabled;
  }

  public void setOptimizeDeleteOperationsEnabled(boolean isOptimizeDeleteOperationsEnabled) {
    this.isOptimizeDeleteOperationsEnabled = isOptimizeDeleteOperationsEnabled;
  }
 
}
TOP

Related Classes of org.activiti.engine.impl.db.DbSqlSession

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.