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