/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.ode.bpel.engine.migration; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ode.bpel.engine.BpelProcess; import org.apache.ode.bpel.engine.Contexts; /** * Checks database schema versions and migrates when necessary. */ public class MigrationHandler { private static final Log __log = LogFactory.getLog(MigrationHandler.class); public static final int CURRENT_SCHEMA_VERSION = 6; private Contexts _contexts; private List<MigrationLink> migrationLinks = new ArrayList<MigrationLink>() {{ add(new MigrationLink(1, 2, new Migration[] { new CorrelatorsMigration(), new CorrelationKeyMigration() } )); add(new MigrationLink(2, 3, new Migration[] { new CorrelationKeySetMigration() } )); add(new MigrationLink(4, 3, new Migration[] { new CorrelationKeySetMigration() } )); add(new MigrationLink(3, 5, new Migration[] { new CorrelationKeySetDataMigration() } )); add(new MigrationLink(5, 6, new Migration[] { new OutstandingRequestsMigration() } )); }}; public MigrationHandler(Contexts _contexts) { this._contexts = _contexts; } public boolean migrate(final Set<BpelProcess> registeredProcesses, int migrationTransactionTimeout) { if (_contexts.dao.getConnection() == null) { __log.debug("No datasource available, stopping migration. Probably running fully in-memory."); return true; } final int version; try { version = getDbVersion(); } catch (Throwable e) { __log.info("The ODE_SCHEMA_VERSION database table doesn't exist. Unless you need to migrate your data" + "from a past version, this message can be safely ignored."); return false; } if (version == -1) { __log.info("No schema version available from the database, migrations will be skipped."); return true; } if (version == CURRENT_SCHEMA_VERSION) return true; try { boolean success = _contexts.scheduler.execTransaction(new Callable<Boolean>() { public Boolean call() throws Exception { ArrayList<Migration> migrations = new ArrayList<Migration>(); findMigrations(version, CURRENT_SCHEMA_VERSION, migrations); if (migrations.size() == 0) { __log.error("Don't know how to migrate from " + version + " to " + CURRENT_SCHEMA_VERSION + ", aborting"); return false; } else { boolean success = true; for (Migration mig : migrations) { __log.debug("Running migration " + mig); success = mig.migrate(registeredProcesses, _contexts.dao.getConnection()) && success; } if (!success) _contexts.scheduler.setRollbackOnly(); else setDbVersion(CURRENT_SCHEMA_VERSION); return success; } } }, migrationTransactionTimeout); return success; } catch (Exception e) { __log.error("An error occured while migrating your database to a newer version of ODE, changes have " + "been aborted", e); throw new RuntimeException(e); } } private static class MigrationLink { int source; int target; Migration[] migrations; public MigrationLink(int source, int target, Migration[] migrations) { this.source = source; this.target = target; this.migrations = migrations; } } /** * Attempts to find a way from a source to a target and collects the migrations found along. Assumes * a directed graph with no loops. Guarantees that migrations are collected in the proper start-to-end * order. */ private boolean findMigrations(int source, int target, List<Migration> ms) { List<MigrationLink> l = findLinksTo(target); for (MigrationLink link : l) { if (link.source == source || findMigrations(source, link.source, ms)) { ms.addAll(Arrays.asList(link.migrations)); return true; } } return false; } /** * Finds all the links with a given target. */ private List<MigrationLink> findLinksTo(int target) { ArrayList<MigrationLink> mls = new ArrayList<MigrationLink>(); for (MigrationLink ml : migrationLinks) { if (ml.target == target) mls.add(ml); } return mls; } private int getDbVersion() { int version = -1; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = null; //FIXME jpa refactoring work leftover stmt = conn.prepareStatement("SELECT VERSION FROM ODE_SCHEMA_VERSION"); rs = stmt.executeQuery(); if (rs.next()) version = rs.getInt("VERSION"); } catch (Exception e) { // Swallow, we'll just abort based on the version number } finally { try { if (rs != null) rs.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } } return version; } private void setDbVersion(int version) { Connection conn = null; Statement stmt = null; try { conn = null;//FIXME jpa refactoring work leftover stmt = conn.createStatement(); int res = stmt.executeUpdate("UPDATE ODE_SCHEMA_VERSION SET VERSION = " + version); // This should never happen but who knows? if (res == 0) throw new RuntimeException("Couldn't update schema version."); } catch (Exception e) { // Swallow, we'll just abort based on the version number } finally { try { if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } }