001/* 002 * (C) Copyright 2006-2015 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 */ 019package org.nuxeo.ecm.core.test; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertFalse; 023import static org.junit.Assert.assertNotEquals; 024import static org.junit.Assert.assertNotNull; 025import static org.junit.Assert.assertTrue; 026 027import java.net.URL; 028import java.net.UnknownHostException; 029import java.sql.SQLException; 030import java.util.Calendar; 031import java.util.function.BiConsumer; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.ecm.core.storage.dbs.DBSRepositoryBase.IdType; 037import org.nuxeo.ecm.core.storage.mongodb.MongoDBRepository; 038import org.nuxeo.ecm.core.storage.mongodb.MongoDBRepositoryDescriptor; 039import org.nuxeo.ecm.core.storage.sql.DatabaseDB2; 040import org.nuxeo.ecm.core.storage.sql.DatabaseDerby; 041import org.nuxeo.ecm.core.storage.sql.DatabaseH2; 042import org.nuxeo.ecm.core.storage.sql.DatabaseHelper; 043import org.nuxeo.ecm.core.storage.sql.DatabaseMySQL; 044import org.nuxeo.ecm.core.storage.sql.DatabaseOracle; 045import org.nuxeo.ecm.core.storage.sql.DatabasePostgreSQL; 046import org.nuxeo.ecm.core.storage.sql.DatabaseSQLServer; 047import org.nuxeo.runtime.api.Framework; 048import org.nuxeo.runtime.test.runner.FeaturesRunner; 049import org.nuxeo.runtime.test.runner.RuntimeFeature; 050import org.nuxeo.runtime.test.runner.RuntimeHarness; 051import org.osgi.framework.Bundle; 052 053import com.mongodb.BasicDBObject; 054import com.mongodb.DBCollection; 055import com.mongodb.MongoClient; 056 057/** 058 * Description of the specific capabilities of a repository for tests, and helper methods. 059 * 060 * @since 7.3 061 */ 062public class StorageConfiguration { 063 064 private static final Log log = LogFactory.getLog(StorageConfiguration.class); 065 066 public static final String CORE_PROPERTY = "nuxeo.test.core"; 067 068 public static final String CORE_VCS = "vcs"; 069 070 public static final String CORE_MEM = "mem"; 071 072 public static final String CORE_MONGODB = "mongodb"; 073 074 public static final String DEFAULT_CORE = CORE_VCS; 075 076 private static final String MONGODB_SERVER_PROPERTY = "nuxeo.test.mongodb.server"; 077 078 private static final String MONGODB_DBNAME_PROPERTY = "nuxeo.test.mongodb.dbname"; 079 080 private static final String DEFAULT_MONGODB_SERVER = "localhost:27017"; 081 082 private static final String DEFAULT_MONGODB_DBNAME = "unittests"; 083 084 private String coreType; 085 086 private boolean isVCS; 087 088 private boolean isDBS; 089 090 private DatabaseHelper databaseHelper; 091 092 final CoreFeature feature; 093 094 public StorageConfiguration(CoreFeature feature) { 095 coreType = defaultSystemProperty(CORE_PROPERTY, DEFAULT_CORE); 096 this.feature = feature; 097 } 098 099 protected static String defaultSystemProperty(String name, String def) { 100 String value = System.getProperty(name); 101 if (value == null || value.equals("") || value.equals("${" + name + "}")) { 102 System.setProperty(name, value = def); 103 } 104 return value; 105 } 106 107 protected static String defaultProperty(String name, String def) { 108 String value = System.getProperty(name); 109 if (value == null || value.equals("") || value.equals("${" + name + "}")) { 110 value = def; 111 } 112 Framework.getProperties().setProperty(name, value); 113 return value; 114 } 115 116 protected void init() { 117 initJDBC(); 118 switch (coreType) { 119 case CORE_VCS: 120 isVCS = true; 121 break; 122 case CORE_MEM: 123 isDBS = true; 124 break; 125 case CORE_MONGODB: 126 isDBS = true; 127 initMongoDB(); 128 break; 129 default: 130 throw new ExceptionInInitializerError("Unknown test core mode: " + coreType); 131 } 132 } 133 134 protected void initJDBC() { 135 databaseHelper = DatabaseHelper.DATABASE; 136 137 String msg = "Deploying JDBC using " + databaseHelper.getClass().getSimpleName(); 138 // System.out used on purpose, don't remove 139 System.out.println(getClass().getSimpleName() + ": " + msg); 140 log.info(msg); 141 142 // setup system properties for generic XML extension points 143 // this is used both for VCS (org.nuxeo.ecm.core.storage.sql.RepositoryService) 144 // and DataSources (org.nuxeo.runtime.datasource) extension points 145 try { 146 databaseHelper.setUp(); 147 } catch (SQLException e) { 148 throw new NuxeoException(e); 149 } 150 } 151 152 protected void initMongoDB() { 153 String server = defaultProperty(MONGODB_SERVER_PROPERTY, DEFAULT_MONGODB_SERVER); 154 String dbname = defaultProperty(MONGODB_DBNAME_PROPERTY, DEFAULT_MONGODB_DBNAME); 155 MongoDBRepositoryDescriptor descriptor = new MongoDBRepositoryDescriptor(); 156 descriptor.name = getRepositoryName(); 157 descriptor.server = server; 158 descriptor.dbname = dbname; 159 try { 160 clearMongoDB(descriptor); 161 } catch (UnknownHostException e) { 162 throw new NuxeoException(e); 163 } 164 } 165 166 protected void clearMongoDB(MongoDBRepositoryDescriptor descriptor) throws UnknownHostException { 167 MongoClient mongoClient = MongoDBRepository.newMongoClient(descriptor); 168 try { 169 DBCollection coll = MongoDBRepository.getCollection(descriptor, mongoClient); 170 coll.dropIndexes(); 171 coll.remove(new BasicDBObject()); 172 coll = MongoDBRepository.getCountersCollection(descriptor, mongoClient); 173 coll.dropIndexes(); 174 coll.remove(new BasicDBObject()); 175 } finally { 176 mongoClient.close(); 177 } 178 } 179 180 public boolean isVCS() { 181 return isVCS; 182 } 183 184 public boolean isVCSH2() { 185 return isVCS && databaseHelper instanceof DatabaseH2; 186 } 187 188 public boolean isVCSDerby() { 189 return isVCS && databaseHelper instanceof DatabaseDerby; 190 } 191 192 public boolean isVCSPostgreSQL() { 193 return isVCS && databaseHelper instanceof DatabasePostgreSQL; 194 } 195 196 public boolean isVCSMySQL() { 197 return isVCS && databaseHelper instanceof DatabaseMySQL; 198 } 199 200 public boolean isVCSOracle() { 201 return isVCS && databaseHelper instanceof DatabaseOracle; 202 } 203 204 public boolean isVCSSQLServer() { 205 return isVCS && databaseHelper instanceof DatabaseSQLServer; 206 } 207 208 public boolean isVCSDB2() { 209 return isVCS && databaseHelper instanceof DatabaseDB2; 210 } 211 212 public boolean isDBS() { 213 return isDBS; 214 } 215 216 public boolean isDBSMem() { 217 return isDBS && CORE_MEM.equals(coreType); 218 } 219 220 public boolean isDBSMongoDB() { 221 return isDBS && CORE_MONGODB.equals(coreType); 222 } 223 224 public String getRepositoryName() { 225 return "test"; 226 } 227 228 /** 229 * For databases that do asynchronous fulltext indexing, sleep a bit. 230 */ 231 public void sleepForFulltext() { 232 if (isVCS()) { 233 databaseHelper.sleepForFulltext(); 234 } else { 235 // DBS 236 } 237 } 238 239 /** 240 * For databases that don't have sub-second resolution, sleep a bit to get to the next second. 241 */ 242 public void maybeSleepToNextSecond() { 243 if (isVCS()) { 244 databaseHelper.maybeSleepToNextSecond(); 245 } else { 246 // DBS 247 } 248 // sleep 1 ms nevertheless to have different timestamps 249 try { 250 Thread.sleep(1); 251 } catch (InterruptedException e) { 252 Thread.currentThread().interrupt(); // restore interrupted status 253 throw new RuntimeException(e); 254 } 255 } 256 257 /** 258 * Checks if the database has sub-second resolution. 259 */ 260 public boolean hasSubSecondResolution() { 261 if (isVCS()) { 262 return databaseHelper.hasSubSecondResolution(); 263 } else { 264 return true; // DBS 265 } 266 } 267 268 public void waitForAsyncCompletion() { 269 feature.waitForAsyncCompletion(); 270 } 271 272 public void waitForFulltextIndexing() { 273 waitForAsyncCompletion(); 274 sleepForFulltext(); 275 } 276 277 /** 278 * Checks if the database supports multiple fulltext indexes. 279 */ 280 public boolean supportsMultipleFulltextIndexes() { 281 if (isVCS()) { 282 return databaseHelper.supportsMultipleFulltextIndexes(); 283 } else { 284 return false; // DBS 285 } 286 } 287 288 public URL getBlobManagerContrib(FeaturesRunner runner) { 289 String bundleName = "org.nuxeo.ecm.core.test"; 290 String contribPath = "OSGI-INF/test-storage-blob-contrib.xml"; 291 RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness(); 292 Bundle bundle = harness.getOSGiAdapter().getRegistry().getBundle(bundleName); 293 URL contribURL = bundle.getEntry(contribPath); 294 assertNotNull("deployment contrib " + contribPath + " not found", contribURL); 295 return contribURL; 296 } 297 298 public URL getRepositoryContrib(FeaturesRunner runner) { 299 String msg; 300 if (isVCS()) { 301 msg = "Deploying a VCS repository"; 302 } else if (isDBS()) { 303 msg = "Deploying a DBS repository using " + coreType; 304 } else { 305 throw new NuxeoException("Unkown test configuration (not vcs/dbs)"); 306 } 307 // System.out used on purpose, don't remove 308 System.out.println(getClass().getSimpleName() + ": " + msg); 309 log.info(msg); 310 311 String contribPath; 312 String bundleName; 313 if (isVCS()) { 314 bundleName = "org.nuxeo.ecm.core.storage.sql.test"; 315 contribPath = databaseHelper.getDeploymentContrib(); 316 } else { 317 bundleName = "org.nuxeo.ecm.core.test"; 318 if (isDBSMem()) { 319 contribPath = "OSGI-INF/test-storage-repo-mem-contrib.xml"; 320 } else if (isDBSMongoDB()) { 321 contribPath = "OSGI-INF/test-storage-repo-mongodb-contrib.xml"; 322 } else { 323 throw new NuxeoException("Unkown DBS test configuration (not mem/mongodb)"); 324 } 325 } 326 RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness(); 327 Bundle bundle = harness.getOSGiAdapter().getRegistry().getBundle(bundleName); 328 URL contribURL = bundle.getEntry(contribPath); 329 assertNotNull("deployment contrib " + contribPath + " not found", contribURL); 330 return contribURL; 331 } 332 333 public void assertEqualsTimestamp(Calendar expected, Calendar actual) { 334 assertEquals(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 335 } 336 337 public void assertNotEqualsTimestamp(Calendar expected, Calendar actual) { 338 assertNotEquals(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 339 } 340 341 /** 342 * Due to some DB restriction this method could fire a false negative. For example 1001ms is before 1002ms but it's 343 * not the case for MySQL (they're equals). 344 */ 345 public void assertBeforeTimestamp(Calendar expected, Calendar actual) { 346 BiConsumer<Calendar, Calendar> assertTrue = (exp, act) -> assertTrue( 347 String.format("expected=%s is not before actual=%s", exp, act), exp.before(act)); 348 assertTrue.accept(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 349 } 350 351 public void assertNotBeforeTimestamp(Calendar expected, Calendar actual) { 352 BiConsumer<Calendar, Calendar> assertFalse = (exp, act) -> assertFalse( 353 String.format("expected=%s is before actual=%s", exp, act), exp.before(act)); 354 assertFalse.accept(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 355 } 356 357 /** 358 * Due to some DB restriction this method could fire a false negative. For example 1002ms is after 1001ms but it's 359 * not the case for MySQL (they're equals). 360 */ 361 public void assertAfterTimestamp(Calendar expected, Calendar actual) { 362 BiConsumer<Calendar, Calendar> assertTrue = (exp, act) -> assertTrue( 363 String.format("expected=%s is not after actual=%s", exp, act), exp.after(act)); 364 assertTrue.accept(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 365 } 366 367 public void assertNotAfterTimestamp(Calendar expected, Calendar actual) { 368 BiConsumer<Calendar, Calendar> assertFalse = (exp, act) -> assertFalse( 369 String.format("expected=%s is after actual=%s", exp, act), exp.after(act)); 370 assertFalse.accept(convertToStoredCalendar(expected), convertToStoredCalendar(actual)); 371 } 372 373 private Calendar convertToStoredCalendar(Calendar calendar) { 374 if (isVCSMySQL() || isVCSSQLServer()) { 375 Calendar result = (Calendar) calendar.clone(); 376 result.setTimeInMillis(convertToStoredTimestamp(result.getTimeInMillis())); 377 return result; 378 } 379 return calendar; 380 } 381 382 private long convertToStoredTimestamp(long timestamp) { 383 if (isVCSMySQL()) { 384 return timestamp / 1000 * 1000; 385 } else if (isVCSSQLServer()) { 386 // as datetime in SQL Server are rounded to increments of .000, .003, or .007 seconds 387 // see https://msdn.microsoft.com/en-us/library/aa258277(SQL.80).aspx 388 long milliseconds = timestamp % 10; 389 long newTimestamp = timestamp - milliseconds; 390 if (milliseconds == 9) { 391 newTimestamp += 10; 392 } else if (milliseconds >= 5) { 393 newTimestamp += 7; 394 } else if (milliseconds >= 2) { 395 newTimestamp += 3; 396 } 397 return newTimestamp; 398 } 399 return timestamp; 400 } 401 402}