/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Florent Guillaume
* Benoit Delbosc
*/
package org.nuxeo.ecm.core.storage.sql;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.JDBCUtils;
import org.nuxeo.ecm.core.blob.binary.BinaryManager;
import org.nuxeo.ecm.core.blob.binary.DefaultBinaryManager;
import org.nuxeo.runtime.RuntimeServiceEvent;
import org.nuxeo.runtime.RuntimeServiceListener;
import org.nuxeo.runtime.api.Framework;
public abstract class DatabaseHelper {
private static final Log log = LogFactory.getLog(DatabaseHelper.class);
public static final String DB_PROPERTY = "nuxeo.test.vcs.db";
public static final String DB_DEFAULT = "H2";
public static final String DEF_ID_TYPE = "varchar"; // "varchar", "uuid", "sequence"
public static DatabaseHelper DATABASE;
public static final String DB_CLASS_NAME_BASE = "org.nuxeo.ecm.core.storage.sql.Database";
protected static final Class<? extends BinaryManager> defaultBinaryManager = DefaultBinaryManager.class;
static {
setSystemProperty(DB_PROPERTY, DB_DEFAULT);
String className = System.getProperty(DB_PROPERTY);
if (className.indexOf('.') < 0) {
className = DB_CLASS_NAME_BASE + className;
}
setDatabaseForTests(className);
}
// available for JDBC tests
public static final String DRIVER_PROPERTY = "nuxeo.test.vcs.driver";
// available for JDBC tests
public static final String URL_PROPERTY = "nuxeo.test.vcs.url";
public static final String SERVER_PROPERTY = "nuxeo.test.vcs.server";
public static final String PORT_PROPERTY = "nuxeo.test.vcs.port";
public static final String DATABASE_PROPERTY = "nuxeo.test.vcs.database";
public static final String USER_PROPERTY = "nuxeo.test.vcs.user";
public static final String PASSWORD_PROPERTY = "nuxeo.test.vcs.password";
public static final String ID_TYPE_PROPERTY = "nuxeo.test.vcs.idtype";
protected Error owner;
public static String setSystemProperty(String name, String def) {
String value = System.getProperty(name);
if (value == null || value.equals("") || value.equals("${" + name + "}")) {
System.setProperty(name, def);
}
return value;
}
public static String setProperty(String name, String def) {
String value = System.getProperty(name);
if (value == null || value.equals("") || value.equals("${" + name + "}")) {
value = def;
}
Framework.getProperties().setProperty(name, value);
return value;
}
public static final String DEFAULT_DATABASE_NAME = "nuxeojunittests";
public String databaseName = DEFAULT_DATABASE_NAME;
public void setDatabaseName(String name) {
databaseName = name;
}
/**
* Sets the database backend used for VCS unit tests.
*/
public static void setDatabaseForTests(String className) {
try {
DATABASE = (DatabaseHelper) Class.forName(className).newInstance();
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError("Database class not found: " + className);
}
}
/**
* Gets a database connection, retrying if the server says it's overloaded.
*
* @since 5.9.3
*/
public static Connection getConnection(String url, String user, String password) throws SQLException {
return JDBCUtils.getConnection(url, user, password);
}
/**
* Executes one statement on all the tables in a database.
*/
public static void doOnAllTables(Connection connection, String catalog, String schemaPattern, String statement)
throws SQLException {
DatabaseMetaData metadata = connection.getMetaData();
List<String> tableNames = new LinkedList<String>();
Set<String> truncateFirst = new HashSet<String>();
ResultSet rs = metadata.getTables(catalog, schemaPattern, "%", new String[] { "TABLE" });
while (rs.next()) {
String tableName = rs.getString("TABLE_NAME");
if (tableName.indexOf('$') != -1) {
// skip Oracle 10g flashback/fulltext-index tables
continue;
}
if (tableName.toLowerCase().startsWith("trace_xe_")) {
// Skip mssql 2012 system table
continue;
}
if ("ACLR_USER_USERS".equals(tableName)) {
// skip nested table that is dropped by the main table
continue;
}
if ("ANCESTORS_ANCESTORS".equals(tableName)) {
// skip nested table that is dropped by the main table
continue;
}
if ("ACLR_MODIFIED".equals(tableName) && DATABASE instanceof DatabaseOracle) {
// global temporary table on Oracle, must TRUNCATE before DROP
truncateFirst.add(tableName);
}
tableNames.add(tableName);
}
// not all databases can cascade on drop
// remove hierarchy last because of foreign keys
if (tableNames.remove("HIERARCHY")) {
tableNames.add("HIERARCHY");
}
// needed for Azure
if (tableNames.remove("NXP_LOGS")) {
tableNames.add("NXP_LOGS");
}
if (tableNames.remove("NXP_LOGS_EXTINFO")) {
tableNames.add("NXP_LOGS_EXTINFO");
}
// PostgreSQL is lowercase
if (tableNames.remove("hierarchy")) {
tableNames.add("hierarchy");
}
Statement st = connection.createStatement();
for (String tableName : tableNames) {
if (truncateFirst.contains(tableName)) {
String sql = String.format("TRUNCATE TABLE \"%s\"", tableName);
executeSql(st, sql);
}
String sql = String.format(statement, tableName);
executeSql(st, sql);
}
st.close();
}
protected static void executeSql(Statement st, String sql) throws SQLException {
log.trace("SQL: " + sql);
st.execute(sql);
}
public void setUp() throws SQLException {
setOwner();
setDatabaseName(DEFAULT_DATABASE_NAME);
setBinaryManager(defaultBinaryManager, "");
Framework.addListener(new RuntimeServiceListener() {
@Override
public void handleEvent(RuntimeServiceEvent event) {
if (RuntimeServiceEvent.RUNTIME_STOPPED == event.id) {
try {
tearDown();
} catch (SQLException cause) {
throw new AssertionError("Cannot teardown database", cause);
}
}
}
});
}
protected void setOwner() {
if (owner != null) {
Error e = new Error("Second call to setUp() without tearDown()", owner);
log.fatal(e.getMessage(), e);
throw e;
}
owner = new Error("Database not released");
}
/**
* @throws SQLException
*/
public void tearDown() throws SQLException {
owner = null;
}
public static void setBinaryManager(Class<? extends BinaryManager> binaryManagerClass, String key) {
setProperty("nuxeo.test.vcs.binary-manager", binaryManagerClass.getName());
setProperty("nuxeo.test.vcs.binary-manager-key", key);
}
public abstract String getDeploymentContrib();
public abstract RepositoryDescriptor getRepositoryDescriptor();
/**
* For databases that do asynchronous fulltext indexing, sleep a bit.
*/
public void sleepForFulltext() {
}
/**
* For databases that fail to cascade deletes beyond a certain depth.
*/
public int getRecursiveRemovalDepthLimit() {
return 0;
}
/**
* For databases that don't support clustering.
*/
public boolean supportsClustering() {
return false;
}
public boolean supportsMultipleFulltextIndexes() {
return true;
}
public boolean supportsXA() {
return true;
}
public boolean supportsSoftDelete() {
return false;
}
/**
* Whether this database supports "sequence" as an id type.
*
* @since 5.9.3
*/
public boolean supportsSequenceId() {
return false;
}
public boolean supportsArrayColumns() {
return false;
}
}