/** * 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.cxf.ws.rm.persistence.jdbc; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; /** * Tests the automatic table updating of RMTxStore that allows compatible changes * in the database tables. */ public class RMTxStoreUpgradeTest extends Assert { private static final String CREATE_OLD_SRC_SEQ_TABLE_STMT = "CREATE TABLE CXF_RM_SRC_SEQUENCES " + "(SEQ_ID VARCHAR(256) NOT NULL, " + "CUR_MSG_NO DECIMAL(19, 0) DEFAULT 1 NOT NULL, " + "LAST_MSG CHAR(1), " + "EXPIRY DECIMAL(19, 0), " + "OFFERING_SEQ_ID VARCHAR(256), " + "ENDPOINT_ID VARCHAR(1024), " + "PRIMARY KEY (SEQ_ID))"; private static final String CREATE_OLD_DEST_SEQ_TABLE_STMT = "CREATE TABLE CXF_RM_DEST_SEQUENCES " + "(SEQ_ID VARCHAR(256) NOT NULL, " + "ACKS_TO VARCHAR(1024) NOT NULL, " + "LAST_MSG_NO DECIMAL(19, 0), " + "ENDPOINT_ID VARCHAR(1024), " + "ACKNOWLEDGED BLOB, " + "PRIMARY KEY (SEQ_ID))"; private static final String CREATE_OLD_MSGS_TABLE_STMT = "CREATE TABLE {0} " + "(SEQ_ID VARCHAR(256) NOT NULL, " + "MSG_NO DECIMAL(19, 0) NOT NULL, " + "SEND_TO VARCHAR(256), " + "CONTENT BLOB, " + "PRIMARY KEY (SEQ_ID, MSG_NO))"; private static final String INBOUND_MSGS_TABLE_NAME = "CXF_RM_INBOUND_MESSAGES"; private static final String OUTBOUND_MSGS_TABLE_NAME = "CXF_RM_OUTBOUND_MESSAGES"; private static final String TEST_DB_NAME = "rmdbu"; @BeforeClass public static void setUpOnce() { RMTxStore.deleteDatabaseFiles(TEST_DB_NAME, true); } @AfterClass public static void tearDownOnce() { RMTxStore.deleteDatabaseFiles(TEST_DB_NAME, false); } @Test public void testUpgradeTables() throws Exception { TestRMTxStore store = new TestRMTxStore(); store.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver"); // workaround for the db file deletion problem during the tests store.setUrl(MessageFormat.format("jdbc:derby:{0};create=true", TEST_DB_NAME)); // use the old db definitions to create the tables store.init(); // verify the absence of the new columns in the tables verifyColumns(store, "CXF_RM_SRC_SEQUENCES", new String[]{"PROTOCOL_VERSION"}, true); verifyColumns(store, "CXF_RM_DEST_SEQUENCES", new String[]{"PROTOCOL_VERSION"}, true); verifyColumns(store, INBOUND_MSGS_TABLE_NAME, new String[]{}, true); verifyColumns(store, OUTBOUND_MSGS_TABLE_NAME, new String[]{}, true); // upgrade the tables and add new columns to the old tables store.upgrade(); store.init(); // verify the presence of the new columns in the upgraded tables verifyColumns(store, "CXF_RM_SRC_SEQUENCES", new String[]{"PROTOCOL_VERSION"}, false); verifyColumns(store, "CXF_RM_DEST_SEQUENCES", new String[]{"PROTOCOL_VERSION"}, false); verifyColumns(store, INBOUND_MSGS_TABLE_NAME, new String[]{}, false); verifyColumns(store, OUTBOUND_MSGS_TABLE_NAME, new String[]{}, false); } private static void verifyColumns(RMTxStore store, String tableName, String[] cols, boolean absent) throws Exception { // verify the presence of the new fields DatabaseMetaData metadata = store.getConnection().getMetaData(); ResultSet rs = metadata.getColumns(null, null, tableName, "%"); Set<String> colNames = new HashSet<>(); Collections.addAll(colNames, cols); while (rs.next()) { colNames.remove(rs.getString(4)); } if (absent) { assertEquals("Some new columns are already present", cols.length, colNames.size()); } else { assertEquals("Some new columns are still absent", 0, colNames.size()); } } static class TestRMTxStore extends RMTxStore { private boolean upgraded; public void upgrade() { upgraded = true; } @Override public synchronized void init() { if (upgraded) { super.init(); } else { // just create the old tables try { setConnection(DriverManager.getConnection(getUrl())); createTables(); } catch (SQLException e) { // ignore this error } } } @Override protected void createTables() throws SQLException { if (upgraded) { super.createTables(); return; } // creating the old tables Statement stmt = null; stmt = getConnection().createStatement(); try { stmt.executeUpdate(CREATE_OLD_SRC_SEQ_TABLE_STMT); } catch (SQLException ex) { if (!isTableExistsError(ex)) { throw ex; } } stmt.close(); stmt = getConnection().createStatement(); try { stmt.executeUpdate(CREATE_OLD_DEST_SEQ_TABLE_STMT); } catch (SQLException ex) { if (!isTableExistsError(ex)) { throw ex; } } stmt.close(); for (String tableName : new String[] {INBOUND_MSGS_TABLE_NAME, OUTBOUND_MSGS_TABLE_NAME}) { stmt = getConnection().createStatement(); try { stmt.executeUpdate(MessageFormat.format(CREATE_OLD_MSGS_TABLE_STMT, tableName)); } catch (SQLException ex) { if (!isTableExistsError(ex)) { throw ex; } } stmt.close(); } } } }