001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 * Benoit Delbosc 019 */ 020 021package org.nuxeo.ecm.core.storage.sql; 022 023import java.sql.Connection; 024import java.sql.DatabaseMetaData; 025import java.sql.ResultSet; 026import java.sql.SQLException; 027import java.sql.Statement; 028import java.util.HashSet; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Set; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.common.utils.JDBCUtils; 036import org.nuxeo.ecm.core.blob.binary.BinaryManager; 037import org.nuxeo.ecm.core.blob.binary.DefaultBinaryManager; 038import org.nuxeo.runtime.RuntimeServiceEvent; 039import org.nuxeo.runtime.RuntimeServiceListener; 040import org.nuxeo.runtime.api.Framework; 041 042public abstract class DatabaseHelper { 043 044 private static final Log log = LogFactory.getLog(DatabaseHelper.class); 045 046 public static final String DB_PROPERTY = "nuxeo.test.vcs.db"; 047 048 public static final String DB_DEFAULT = "H2"; 049 050 public static final String DEF_ID_TYPE = "varchar"; // "varchar", "uuid", "sequence" 051 052 public static DatabaseHelper DATABASE; 053 054 public static final String DB_CLASS_NAME_BASE = "org.nuxeo.ecm.core.storage.sql.Database"; 055 056 protected static final Class<? extends BinaryManager> defaultBinaryManager = DefaultBinaryManager.class; 057 058 static { 059 setSystemProperty(DB_PROPERTY, DB_DEFAULT); 060 String className = System.getProperty(DB_PROPERTY); 061 if (className.indexOf('.') < 0) { 062 className = DB_CLASS_NAME_BASE + className; 063 } 064 setDatabaseForTests(className); 065 } 066 067 // available for JDBC tests 068 public static final String DRIVER_PROPERTY = "nuxeo.test.vcs.driver"; 069 070 // available for JDBC tests 071 public static final String URL_PROPERTY = "nuxeo.test.vcs.url"; 072 073 public static final String SERVER_PROPERTY = "nuxeo.test.vcs.server"; 074 075 public static final String PORT_PROPERTY = "nuxeo.test.vcs.port"; 076 077 public static final String DATABASE_PROPERTY = "nuxeo.test.vcs.database"; 078 079 public static final String USER_PROPERTY = "nuxeo.test.vcs.user"; 080 081 public static final String PASSWORD_PROPERTY = "nuxeo.test.vcs.password"; 082 083 public static final String ID_TYPE_PROPERTY = "nuxeo.test.vcs.idtype"; 084 085 protected Error owner; 086 087 public static String setSystemProperty(String name, String def) { 088 String value = System.getProperty(name); 089 if (value == null || value.equals("") || value.equals("${" + name + "}")) { 090 System.setProperty(name, def); 091 } 092 return value; 093 } 094 095 public static String setProperty(String name, String def) { 096 String value = System.getProperty(name); 097 if (value == null || value.equals("") || value.equals("${" + name + "}")) { 098 value = def; 099 } 100 Framework.getProperties().setProperty(name, value); 101 return value; 102 } 103 104 public static final String DEFAULT_DATABASE_NAME = "nuxeojunittests"; 105 106 public String databaseName = DEFAULT_DATABASE_NAME; 107 108 public void setDatabaseName(String name) { 109 databaseName = name; 110 } 111 112 /** 113 * Sets the database backend used for VCS unit tests. 114 */ 115 public static void setDatabaseForTests(String className) { 116 try { 117 DATABASE = (DatabaseHelper) Class.forName(className).newInstance(); 118 } catch (ReflectiveOperationException e) { 119 throw new ExceptionInInitializerError("Database class not found: " + className); 120 } 121 } 122 123 /** 124 * Gets a database connection, retrying if the server says it's overloaded. 125 * 126 * @since 5.9.3 127 */ 128 public static Connection getConnection(String url, String user, String password) throws SQLException { 129 return JDBCUtils.getConnection(url, user, password); 130 } 131 132 /** 133 * Executes one statement on all the tables in a database. 134 */ 135 public static void doOnAllTables(Connection connection, String catalog, String schemaPattern, String statement) 136 throws SQLException { 137 DatabaseMetaData metadata = connection.getMetaData(); 138 List<String> tableNames = new LinkedList<String>(); 139 Set<String> truncateFirst = new HashSet<String>(); 140 ResultSet rs = metadata.getTables(catalog, schemaPattern, "%", new String[] { "TABLE" }); 141 while (rs.next()) { 142 String tableName = rs.getString("TABLE_NAME"); 143 if (tableName.indexOf('$') != -1) { 144 // skip Oracle 10g flashback/fulltext-index tables 145 continue; 146 } 147 if (tableName.toLowerCase().startsWith("trace_xe_")) { 148 // Skip mssql 2012 system table 149 continue; 150 } 151 if ("ACLR_USER_USERS".equals(tableName)) { 152 // skip nested table that is dropped by the main table 153 continue; 154 } 155 if ("ANCESTORS_ANCESTORS".equals(tableName)) { 156 // skip nested table that is dropped by the main table 157 continue; 158 } 159 if ("ACLR_MODIFIED".equals(tableName) && DATABASE instanceof DatabaseOracle) { 160 // global temporary table on Oracle, must TRUNCATE before DROP 161 truncateFirst.add(tableName); 162 } 163 tableNames.add(tableName); 164 } 165 // not all databases can cascade on drop 166 // remove hierarchy last because of foreign keys 167 if (tableNames.remove("HIERARCHY")) { 168 tableNames.add("HIERARCHY"); 169 } 170 // needed for Azure 171 if (tableNames.remove("NXP_LOGS")) { 172 tableNames.add("NXP_LOGS"); 173 } 174 if (tableNames.remove("NXP_LOGS_EXTINFO")) { 175 tableNames.add("NXP_LOGS_EXTINFO"); 176 } 177 // PostgreSQL is lowercase 178 if (tableNames.remove("hierarchy")) { 179 tableNames.add("hierarchy"); 180 } 181 Statement st = connection.createStatement(); 182 for (String tableName : tableNames) { 183 if (truncateFirst.contains(tableName)) { 184 String sql = String.format("TRUNCATE TABLE \"%s\"", tableName); 185 executeSql(st, sql); 186 } 187 String sql = String.format(statement, tableName); 188 executeSql(st, sql); 189 } 190 st.close(); 191 } 192 193 protected static void executeSql(Statement st, String sql) throws SQLException { 194 log.trace("SQL: " + sql); 195 st.execute(sql); 196 } 197 198 public void setUp() throws SQLException { 199 setOwner(); 200 setDatabaseName(DEFAULT_DATABASE_NAME); 201 setBinaryManager(defaultBinaryManager, ""); 202 Framework.addListener(new RuntimeServiceListener() { 203 204 @Override 205 public void handleEvent(RuntimeServiceEvent event) { 206 if (RuntimeServiceEvent.RUNTIME_STOPPED == event.id) { 207 try { 208 tearDown(); 209 } catch (SQLException cause) { 210 throw new AssertionError("Cannot teardown database", cause); 211 } 212 } 213 } 214 }); 215 } 216 217 protected void setOwner() { 218 if (owner != null) { 219 Error e = new Error("Second call to setUp() without tearDown()", owner); 220 log.fatal(e.getMessage(), e); 221 throw e; 222 } 223 owner = new Error("Database not released"); 224 } 225 226 /** 227 * @throws SQLException 228 */ 229 public void tearDown() throws SQLException { 230 owner = null; 231 } 232 233 public static void setBinaryManager(Class<? extends BinaryManager> binaryManagerClass, String key) { 234 setProperty("nuxeo.test.vcs.binary-manager", binaryManagerClass.getName()); 235 setProperty("nuxeo.test.vcs.binary-manager-key", key); 236 } 237 238 public abstract String getDeploymentContrib(); 239 240 public abstract RepositoryDescriptor getRepositoryDescriptor(); 241 242 /** 243 * For databases that do asynchronous fulltext indexing, sleep a bit. 244 */ 245 public void sleepForFulltext() { 246 } 247 248 /** 249 * For databases that fail to cascade deletes beyond a certain depth. 250 */ 251 public int getRecursiveRemovalDepthLimit() { 252 return 0; 253 } 254 255 /** 256 * For databases that don't support clustering. 257 */ 258 public boolean supportsClustering() { 259 return false; 260 } 261 262 public boolean supportsMultipleFulltextIndexes() { 263 return true; 264 } 265 266 public boolean supportsXA() { 267 return true; 268 } 269 270 public boolean supportsSoftDelete() { 271 return false; 272 } 273 274 /** 275 * Whether this database supports "sequence" as an id type. 276 * 277 * @since 5.9.3 278 */ 279 public boolean supportsSequenceId() { 280 return false; 281 } 282 283 public boolean supportsArrayColumns() { 284 return false; 285 } 286 287}