All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ow2.bonita.util.DbMigration Maven / Gradle / Ivy

/**
 * Copyright (C) 2010-2012 BonitaSoft S.A.
 * BonitaSoft, 31 rue Gustave Eiffel - 38000 Grenoble
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/
package org.ow2.bonita.util;

import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.Transformers;
import org.ow2.bonita.env.EnvConstants;
import org.ow2.bonita.facade.uuid.ProcessInstanceUUID;
import org.ow2.bonita.runtime.event.EventConstants;
import org.ow2.bonita.runtime.event.EventCouple;
import org.ow2.bonita.runtime.event.IncomingEventInstance;
import org.ow2.bonita.runtime.event.Job;
import org.ow2.bonita.runtime.event.JobBuilder;
import org.ow2.bonita.runtime.event.OutgoingEventInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Anthony Birembaut, Matthieu Chaffotte
 */
public final class DbMigration {

  // FIXME check job name
  private static final Logger LOG = LoggerFactory.getLogger(DbMigration.class);

  private DbMigration() {
  }

  public static void main(final String[] args) throws Exception {
    if (args == null || args.length != 3) {
      final String message = ExceptionManager.getInstance().getFullMessage("bh_DBM_1");
      throw new IllegalArgumentException(message);
    }
    // Check that bonita home is set
    BonitaConstants.getBonitaHomeFolder();
    final String domain = args[0];
    final String db = args[1].toLowerCase();
    final int stage = Integer.valueOf(args[2]);
    final String useSearch = "bonita.search.use";
    System.setProperty(useSearch, "false");
    LOG.info("Starting Migration on tenant: " + domain);
    try {
      if (stage < 2) {
        LOG.info("Stage 1: Updating history database schema...");
        preUpdateDatabase(domain, EnvConstants.HB_CONFIG_HISTORY, db);
        LOG.info("Stage 1: DONE");
      }
      if (stage < 3) {
        LOG.info("Stage 2: Updating journal database schema...");
        preUpdateDatabase(domain, EnvConstants.HB_CONFIG_CORE, db);
        LOG.info("Stage 2: DONE");
      }
      if (stage < 4) {
        LOG.info("Stage 3.1: Migrating asynchronous events...");
        migrateAsyncEvents(domain, EnvConstants.HB_CONFIG_CORE);
        LOG.info("Stage 3.1: DONE");
        LOG.info("Stage 3.2: Migrating timer events...");
        migrateTimerEvents(domain, EnvConstants.HB_CONFIG_CORE);
        LOG.info("Stage 3.2: DONE");
        LOG.info("Stage 3.3: Migrating deadline events...");
        migrateDeadlineEvents(domain, EnvConstants.HB_CONFIG_CORE);
        LOG.info("Stage 3.3: DONE");
        LOG.info("Stage 3.4: Migrating error events...");
        migrateErrorEvents(domain, EnvConstants.HB_CONFIG_CORE);
        LOG.info("Stage 3.4: DONE");
        LOG.info("Stage 3.5: Migrating signal events...");
        migrateSignalEvents(domain, EnvConstants.HB_CONFIG_CORE);
        LOG.info("Stage 3.5: DONE");
      }
      if (stage < 5) {
        LOG.info("Stage 4: Cleaning history database...");
        cleanDatabase(domain, EnvConstants.HB_CONFIG_HISTORY, db);
        postUpdateDatabase(domain, EnvConstants.HB_CONFIG_HISTORY, db);
        LOG.info("Stage 4: DONE");
      }
      if (stage < 6) {
        LOG.info("Stage 5: Cleaning journal database...");
        cleanDatabase(domain, EnvConstants.HB_CONFIG_CORE, db);
        postUpdateDatabase(domain, EnvConstants.HB_CONFIG_CORE, db);
        LOG.info("Stage 5: DONE");
      }
      LOG.info("Migration on tenant " + domain + ": DONE");
    } finally {
      System.clearProperty(useSearch);
    }
  }

  private static void preUpdateDatabase(final String domain, final String configurationName, final String db)
      throws Exception {
    final String path = getSQLScriptPath(db, "pre");
    migrateDb(domain, configurationName, db, path);
  }

  private static void postUpdateDatabase(final String domain, final String configurationName, final String db)
      throws Exception {
    final String path = getSQLScriptPath(db, "post");
    migrateDb(domain, configurationName, db, path);
  }

  private static void cleanDatabase(final String domain, final String configurationName, final String db)
      throws Exception {
    final String path = getSQLScriptPath(db, "clean");
    cleanDb(domain, configurationName, db, path);
  }

  private static String getSQLScriptPath(final String db, final String position) {
    final StringBuilder migrationScript = new StringBuilder("/migration/");
    migrationScript.append(db).append("-").append(position).append("-5.8-5.9.sql");
    return migrationScript.toString();
  }

  private static void cleanDb(final String domain, final String configurationName, final String database,
      final String resourcePath) throws Exception {
    final SessionFactory sessionFactory = DbTool.getSessionFactory(domain,
        configurationName.replaceAll("-configuration", "-session-factory"));
    try {
      InputStream inputStream = null;
      try {
        inputStream = getScriptStream(resourcePath);
        executeScriptStepByStep(sessionFactory, inputStream, database);
      } finally {
        if (inputStream != null) {
          inputStream.close();
        }
      }
    } finally {
      sessionFactory.close();
    }
  }

  public static void migrateDb(final String domain, final String configurationName, final String database,
      final String resourcePath) throws Exception {
    final SessionFactory sessionFactory = DbTool.getSessionFactory(domain,
        configurationName.replaceAll("-configuration", "-session-factory"));
    try {
      InputStream inputStream = null;
      try {
        inputStream = getScriptStream(resourcePath);
        executeScript(sessionFactory, inputStream, database);
      } finally {
        if (inputStream != null) {
          inputStream.close();
        }
      }
    } finally {
      sessionFactory.close();
    }
  }

  private static InputStream getScriptStream(final String resourcePath) {
    final InputStream inputStream = DbMigration.class.getResourceAsStream(resourcePath);
    if (inputStream == null) {
      final String message = ExceptionManager.getInstance().getFullMessage("bh_DBM_2");
      throw new IllegalArgumentException(message);
    }
    return inputStream;
  }

  public static void executeScript(final SessionFactory sessionFactory, final InputStream inputStream, final String db) {
    final byte[] bytes = IoUtil.readBytes(inputStream);
    final String scriptContent = new String(bytes);
    final List commands = getCommands(scriptContent, db);

    final Session session = sessionFactory.openSession();
    session.getTransaction().begin();

    LOG.info("DB Commands Execution: " + commands.size());
    for (final String command : commands) {
      LOG.info("Executing command : " + command);
      try {
        session.createSQLQuery(command).executeUpdate();
      } catch (final Exception e) {
        LOG.error("Error while executing command: " + command, e);
      }
    }
    session.getTransaction().commit();
    session.close();
  }

  private static void executeScriptStepByStep(final SessionFactory sessionFactory, final InputStream inputStream,
      final String db) {
    final byte[] bytes = IoUtil.readBytes(inputStream);
    final String scriptContent = new String(bytes);
    final List commands = getCommands(scriptContent, db);

    final Session session = sessionFactory.openSession();

    LOG.info("DB Commands Execution: " + commands.size());
    for (final String command : commands) {
      LOG.info("Executing command : " + command);
      try {
        session.getTransaction().begin();
        session.createSQLQuery(command).executeUpdate();
        session.getTransaction().commit();
      } catch (final Exception e) {
        session.getTransaction().rollback();
        LOG.warn("Error while executing command: " + command, e);
      }
    }
    session.close();
  }

  public static List getCommands(final String scriptContent, final String db) {
    String delimiter = ";";
    if ("sqlserver".equals(db) || "sybase".equals(db)) {
      delimiter = "go";
    }
    final String regex = delimiter.concat("\r?\n");
    final List commands = new ArrayList();
    final String[] tmp = scriptContent.split(regex);
    for (final String command : tmp) {
      if (command.trim().length() > 0) {
        commands.add(command.trim());
      }
    }
    final int lastIndex = commands.size() - 1;
    if (lastIndex >= 0) {
      String lastCommand = commands.get(lastIndex);
      final int index = lastCommand.lastIndexOf(delimiter);
      if (index > 0) {
        lastCommand = lastCommand.substring(0, index);
        commands.remove(lastIndex);
        commands.add(lastCommand);
      }
    }
    return commands;
  }

  private static void migrateTimerEvents(final String domain, final String configurationName) throws Exception {
    final MigrateTimers timers = new MigrateTimers();
    timers.execute(domain, configurationName);
  }

  private static void migrateSignalEvents(final String domain, final String configurationName) throws Exception {
    final MigrateSignals signals = new MigrateSignals();
    signals.execute(domain, configurationName);
  }

  private static void migrateErrorEvents(final String domain, final String configurationName) throws Exception {
    final MigrateErrors errors = new MigrateErrors();
    errors.execute(domain, configurationName);
  }

  private static void migrateDeadlineEvents(final String domain, final String configurationName) throws Exception {
    final MigrateDeadlines deadlines = new MigrateDeadlines();
    deadlines.execute(domain, configurationName);
  }

  private static void migrateAsyncEvents(final String domain, final String configurationName) throws Exception {
    final MigrateAsyncs asyncs = new MigrateAsyncs();
    asyncs.execute(domain, configurationName);
  }

  private static abstract class ExecuteInASession {

    public abstract String getEventQuery();

    public abstract void executeEventCouple(Session session, EventCouple couple);

    protected int getIncomingRetries(final Session session, final IncomingEventInstance event) {
      final String queryString = "select RETRIES_ from BN_IEI_ where DBID_ = :id";
      final Query query = session.createSQLQuery(queryString);
      query.setLong("id", event.getId());
      final Object result = query.uniqueResult();
      if (result instanceof BigDecimal) {
        return ((BigDecimal) result).intValue();
      } else {
        return (Integer) result;
      }
    }

    protected String getRootProcessInstanceUUID(final Session session, final ProcessInstanceUUID instanceUUID) {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT process.rootInstanceUUID.value ");
      builder.append("FROM org.ow2.bonita.facade.runtime.impl.InternalProcessInstance AS process ");
      builder.append("WHERE process.instanceUUID.value = :processUUID");
      final Query query = session.createQuery(builder.toString());
      query.setString("processUUID", instanceUUID.getValue());
      return (String) query.uniqueResult();
    }

    @SuppressWarnings("unchecked")
    private static List getEvents(final Session session, final String queryString) {
      final Query query = session.createQuery(queryString);
      query.setResultTransformer(Transformers.aliasToBean(EventCouple.class));
      query.setMaxResults(100);
      final List couples = query.list();
      if (couples == null) {
        return Collections.emptyList();
      }
      return couples;
    }

    public void execute(final String domain, final String configurationName) throws Exception {
      final SessionFactory sessionFactory = DbTool.getSessionFactory(domain,
          configurationName.replaceAll("-configuration", "-session-factory"));
      final Session session = sessionFactory.openSession();
      try {
        session.getTransaction().begin();
        List eventCouples = null;
        do {
          eventCouples = getEvents(session, getEventQuery());
          final int size = eventCouples.size();
          int i = 1;
          LOG.info("Getting " + size + " eventCouple(s)");
          for (final EventCouple eventCouple : eventCouples) {
            LOG.info("Migrating eventCouple: " + i + "/" + size);
            executeEventCouple(session, eventCouple);
            session.getTransaction().commit();
            session.getTransaction().begin();
            i++;
          }
        } while (!eventCouples.isEmpty());
      } finally {
        session.close();
      }
    }
  }

  private static class MigrateTimers extends ExecuteInASession {

    @Override
    public String getEventQuery() {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT incoming AS incoming, outgoing AS outgoing ");
      builder.append("FROM org.ow2.bonita.runtime.event.IncomingEventInstance AS incoming,");
      builder.append("     org.ow2.bonita.runtime.event.OutgoingEventInstance AS outgoing ");
      builder.append("WHERE incoming.name = outgoing.name ");
      builder
          .append("AND (incoming.signal = 'end_of_timer' OR incoming.signal = 'event.boundary.timer' OR incoming.signal = 'event.start.timer') ");
      builder.append("AND incoming.locked = outgoing.locked ");
      builder.append("AND (outgoing.processName IS NULL OR outgoing.processName = incoming.processName) ");
      builder.append("AND (outgoing.activityName IS NULL OR outgoing.activityName = incoming.activityName) ");
      builder.append("ORDER BY outgoing.id ASC, incoming.id ASC");
      return builder.toString();
    }

    @Override
    public void executeEventCouple(final Session session, final EventCouple couple) {
      final IncomingEventInstance incoming = couple.getIncoming();
      LOG.info(incoming.toString());
      Job timer = null;
      if ("event.start.timer".equals(incoming.getSignal())) {
        timer = JobBuilder.startTimerJob(incoming.getActivityName(), incoming.getActivityDefinitionUUID(),
            incoming.getExpression(), incoming.getEnableTime());
        timer.setEventSubProcessRootInstanceUUID(incoming.getEventSubProcessRootInstanceUUID());
      } else if ("event.boundary.timer".equals(incoming.getSignal())) {
        final String uuid = getRootProcessInstanceUUID(session, incoming.getInstanceUUID());
        timer = JobBuilder.boundaryTimerJob(incoming.getName(), new ProcessInstanceUUID(uuid),
            incoming.getExecutionUUID(), incoming.getEnableTime(), incoming.getInstanceUUID());
      } else if ("end_of_timer".equals(incoming.getSignal())) {
        final String uuid = getRootProcessInstanceUUID(session, incoming.getInstanceUUID());
        timer = JobBuilder.intermediateTimerJob(incoming.getActivityName(), new ProcessInstanceUUID(uuid),
            incoming.getExecutionUUID(), incoming.getEnableTime(), incoming.getInstanceUUID());
      }
      timer.setRetries(getIncomingRetries(session, incoming));
      session.delete(incoming);
      LOG.info(timer.toString());
      session.save(timer);
      session.delete(couple.getOutgoing());
    }
  }

  private static class MigrateSignals extends ExecuteInASession {

    @Override
    public String getEventQuery() {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT incoming AS incoming, outgoing AS outgoing ");
      builder.append("FROM org.ow2.bonita.runtime.event.IncomingEventInstance AS incoming,");
      builder.append("     org.ow2.bonita.runtime.event.OutgoingEventInstance AS outgoing ");
      builder.append("WHERE incoming.name = outgoing.name ");
      builder
          .append("AND (incoming.signal = 'event.intermediate.signal' OR incoming.signal = 'event.boundary.signal' OR incoming.signal = 'event.start.signal') ");
      builder.append("AND incoming.locked = outgoing.locked ");
      builder.append("AND (outgoing.processName IS NULL OR outgoing.processName = incoming.processName) ");
      builder.append("AND (outgoing.activityName IS NULL OR outgoing.activityName = incoming.activityName) ");
      builder.append("ORDER BY outgoing.id ASC, incoming.id ASC");
      return builder.toString();
    }

    @Override
    public void executeEventCouple(final Session session, final EventCouple couple) {
      final IncomingEventInstance incoming = couple.getIncoming();
      Job signal = null;
      if (EventConstants.SIGNAL_START_EVENT.equals(incoming.getSignal())) {
        signal = JobBuilder.startSignalJob(incoming.getName(), incoming.getActivityDefinitionUUID());
        signal.setEventSubProcessRootInstanceUUID(incoming.getEventSubProcessRootInstanceUUID());
      } else if (EventConstants.SIGNAL_BOUNDARY_EVENT.equals(incoming.getSignal())) {
        final String uuid = getRootProcessInstanceUUID(session, incoming.getInstanceUUID());
        signal = JobBuilder.boundarySignalJob(incoming.getActivityName(), new ProcessInstanceUUID(uuid),
            incoming.getExecutionUUID(), incoming.getInstanceUUID());
        session.delete(incoming);
      } else if (EventConstants.SIGNAL_INTERMEDIATE_EVENT.equals(incoming.getSignal())) {
        final String uuid = getRootProcessInstanceUUID(session, incoming.getInstanceUUID());
        signal = JobBuilder.intermediateSignalJob(incoming.getName(), new ProcessInstanceUUID(uuid),
            incoming.getExecutionUUID(), incoming.getInstanceUUID());
        session.delete(incoming);
      }
      signal.setRetries(getIncomingRetries(session, incoming));
      session.save(signal);
      session.delete(couple.getOutgoing());
    }
  }

  private static class MigrateErrors extends ExecuteInASession {

    @Override
    public String getEventQuery() {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT incoming AS incoming, outgoing AS outgoing ");
      builder.append("FROM org.ow2.bonita.runtime.event.IncomingEventInstance AS incoming,");
      builder.append("     org.ow2.bonita.runtime.event.OutgoingEventInstance AS outgoing ");
      builder.append("WHERE incoming.name = outgoing.name ");
      builder.append("AND (incoming.signal = 'event.boundary.error' OR incoming.signal = 'event.start.error') ");
      builder.append("AND incoming.locked = outgoing.locked ");
      builder.append("AND (outgoing.processName IS NULL OR outgoing.processName = incoming.processName) ");
      builder.append("AND (outgoing.activityName IS NULL OR outgoing.activityName = incoming.activityName) ");
      builder.append("ORDER BY outgoing.id ASC, incoming.id ASC");
      return builder.toString();
    }

    @Override
    public void executeEventCouple(final Session session, final EventCouple couple) {
      final IncomingEventInstance incoming = couple.getIncoming();
      Job error = null;
      if ("event.start.error".equals(incoming.getSignal())) {
        error = JobBuilder.startErrorJob(incoming.getName(), incoming.getActivityDefinitionUUID());
        error.setEventSubProcessRootInstanceUUID(incoming.getEventSubProcessRootInstanceUUID());
      } else if ("event.boundary.error".equals(incoming.getSignal())) {
        final String uuid = getRootProcessInstanceUUID(session, incoming.getInstanceUUID());
        final String incomingEventName = incoming.getName();
        final int separator = incomingEventName.indexOf(EventConstants.SEPARATOR);
        final String eventName = incomingEventName.substring(0, separator);
        error = JobBuilder.boundaryErrorJob(eventName, new ProcessInstanceUUID(uuid), incoming.getExecutionUUID(),
            incoming.getInstanceUUID());
        session.delete(incoming);
      }
      error.setRetries(getIncomingRetries(session, incoming));
      LOG.info(error.toString());
      session.save(error);
      session.delete(couple.getOutgoing());
    }
  }

  private static class MigrateDeadlines extends ExecuteInASession {

    @Override
    public String getEventQuery() {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT incoming AS incoming, outgoing AS outgoing ");
      builder.append("FROM org.ow2.bonita.runtime.event.IncomingEventInstance AS incoming,");
      builder.append("     org.ow2.bonita.runtime.event.OutgoingEventInstance AS outgoing ");
      builder.append("WHERE incoming.name = outgoing.name ");
      builder.append("AND incoming.signal = 'timer' ");
      builder.append("AND incoming.locked = outgoing.locked ");
      builder.append("AND (outgoing.processName IS NULL OR outgoing.processName = incoming.processName) ");
      builder.append("AND (outgoing.activityName IS NULL OR outgoing.activityName = incoming.activityName) ");
      builder.append("ORDER BY outgoing.id ASC, incoming.id ASC");
      return builder.toString();
    }

    @Override
    public void executeEventCouple(final Session session, final EventCouple couple) {
      final IncomingEventInstance incoming = couple.getIncoming();
      final OutgoingEventInstance outgoing = couple.getOutgoing();
      final ProcessInstanceUUID instanceUUID = incoming.getInstanceUUID();
      final String uuid = getRootProcessInstanceUUID(session, instanceUUID);
      final String deadlineId = String.valueOf(outgoing.getParameters().get("id"));
      final Job deadline = JobBuilder.deadlineJob(deadlineId, new ProcessInstanceUUID(uuid),
          incoming.getExecutionUUID(), incoming.getEnableTime(), incoming.getInstanceUUID());
      deadline.setRetries(getIncomingRetries(session, incoming));
      session.save(deadline);
      session.delete(outgoing);
      session.delete(incoming);
    }
  }

  private static class MigrateAsyncs extends ExecuteInASession {

    @Override
    public String getEventQuery() {
      final StringBuilder builder = new StringBuilder();
      builder.append("SELECT incoming AS incoming, outgoing AS outgoing ");
      builder.append("FROM org.ow2.bonita.runtime.event.IncomingEventInstance AS incoming,");
      builder.append("     org.ow2.bonita.runtime.event.OutgoingEventInstance AS outgoing ");
      builder.append("WHERE incoming.name = outgoing.name ");
      builder.append("AND incoming.signal = 'async' ");
      builder.append("AND incoming.locked = outgoing.locked ");
      builder.append("AND (outgoing.processName IS NULL OR outgoing.processName = incoming.processName) ");
      builder.append("AND (outgoing.activityName IS NULL OR outgoing.activityName = incoming.activityName) ");
      builder.append("ORDER BY outgoing.id ASC, incoming.id ASC");
      return builder.toString();
    }

    @Override
    public void executeEventCouple(final Session session, final EventCouple couple) {
      final IncomingEventInstance incoming = couple.getIncoming();
      final OutgoingEventInstance outgoing = couple.getOutgoing();
      final ProcessInstanceUUID instanceUUID = incoming.getInstanceUUID();
      final String uuid = getRootProcessInstanceUUID(session, instanceUUID);
      final Job async = JobBuilder.asyncJob(incoming.getName(), new ProcessInstanceUUID(uuid),
          incoming.getExecutionUUID(), instanceUUID);
      async.setEventSubProcessRootInstanceUUID(incoming.getEventSubProcessRootInstanceUUID());
      async.setRetries(getIncomingRetries(session, incoming));
      session.save(async);
      session.delete(outgoing);
      session.delete(incoming);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy