001/* 002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Bogdan Stefanescu 011 * Florent Guillaume 012 */ 013package org.nuxeo.ecm.core.test; 014 015import static org.junit.Assert.assertNotNull; 016 017import java.io.Serializable; 018import java.net.URL; 019import java.util.ArrayList; 020import java.util.Map; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.nuxeo.ecm.core.api.CoreInstance; 025import org.nuxeo.ecm.core.api.CoreSession; 026import org.nuxeo.ecm.core.api.DocumentNotFoundException; 027import org.nuxeo.ecm.core.api.IdRef; 028import org.nuxeo.ecm.core.api.IterableQueryResult; 029import org.nuxeo.ecm.core.api.NuxeoException; 030import org.nuxeo.ecm.core.api.NuxeoPrincipal; 031import org.nuxeo.ecm.core.api.PathRef; 032import org.nuxeo.ecm.core.api.impl.UserPrincipal; 033import org.nuxeo.ecm.core.event.EventService; 034import org.nuxeo.ecm.core.query.sql.NXQL; 035import org.nuxeo.ecm.core.repository.RepositoryFactory; 036import org.nuxeo.ecm.core.repository.RepositoryService; 037import org.nuxeo.ecm.core.storage.sql.DatabaseHelper; 038import org.nuxeo.ecm.core.test.annotations.Granularity; 039import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; 040import org.nuxeo.ecm.core.test.annotations.RepositoryInit; 041import org.nuxeo.osgi.OSGiAdapter; 042import org.nuxeo.runtime.api.Framework; 043import org.nuxeo.runtime.jtajca.NuxeoContainer; 044import org.nuxeo.runtime.model.persistence.Contribution; 045import org.nuxeo.runtime.model.persistence.fs.ContributionLocation; 046import org.nuxeo.runtime.test.runner.Defaults; 047import org.nuxeo.runtime.test.runner.Deploy; 048import org.nuxeo.runtime.test.runner.Features; 049import org.nuxeo.runtime.test.runner.FeaturesRunner; 050import org.nuxeo.runtime.test.runner.LocalDeploy; 051import org.nuxeo.runtime.test.runner.RuntimeFeature; 052import org.nuxeo.runtime.test.runner.RuntimeHarness; 053import org.nuxeo.runtime.test.runner.ServiceProvider; 054import org.nuxeo.runtime.test.runner.SimpleFeature; 055import org.nuxeo.runtime.transaction.TransactionHelper; 056import org.osgi.framework.Bundle; 057 058import com.google.inject.Scope; 059 060/** 061 * The core feature provides a default {@link CoreSession} that can be injected. 062 * <p> 063 * In addition, by injecting the feature itself, some helper methods are available to open new sessions. 064 */ 065@Deploy({ "org.nuxeo.runtime.management", // 066 "org.nuxeo.ecm.core.schema", // 067 "org.nuxeo.ecm.core.query", // 068 "org.nuxeo.ecm.core.api", // 069 "org.nuxeo.ecm.core.event", // 070 "org.nuxeo.ecm.core", // 071 "org.nuxeo.ecm.core.mimetype", // 072 "org.nuxeo.ecm.core.convert", // 073 "org.nuxeo.ecm.core.convert.plugins", // 074 "org.nuxeo.ecm.core.storage", // 075 "org.nuxeo.ecm.core.storage.sql", // 076 "org.nuxeo.ecm.core.storage.sql.test" // 077}) 078@Features({ RuntimeFeature.class, TransactionalFeature.class }) 079@LocalDeploy("org.nuxeo.ecm.core.event:test-queuing.xml") 080public class CoreFeature extends SimpleFeature { 081 082 private static final Log log = LogFactory.getLog(CoreFeature.class); 083 084 protected StorageConfiguration storageConfiguration = new StorageConfiguration(); 085 086 protected RepositoryInit repositoryInit; 087 088 protected Granularity granularity; 089 090 protected Class<? extends RepositoryFactory> repositoryFactoryClass; 091 092 protected int initialOpenSessions; 093 094 // this value gets injected 095 protected CoreSession session; 096 097 protected boolean cleaned; 098 099 protected class CoreSessionServiceProvider extends ServiceProvider<CoreSession> { 100 public CoreSessionServiceProvider() { 101 super(CoreSession.class); 102 } 103 104 @Override 105 public Scope getScope() { 106 return CoreScope.INSTANCE; 107 } 108 109 @Override 110 public CoreSession get() { 111 return session; 112 } 113 } 114 115 public StorageConfiguration getStorageConfiguration() { 116 return storageConfiguration; 117 } 118 119 @Override 120 public void initialize(FeaturesRunner runner) { 121 runner.getFeature(RuntimeFeature.class).addServiceProvider(new CoreSessionServiceProvider()); 122 // init from RepositoryConfig annotations 123 RepositoryConfig repositoryConfig = runner.getConfig(RepositoryConfig.class); 124 if (repositoryConfig == null) { 125 repositoryConfig = Defaults.of(RepositoryConfig.class); 126 } 127 try { 128 repositoryInit = repositoryConfig.init().newInstance(); 129 } catch (ReflectiveOperationException e) { 130 throw new NuxeoException(e); 131 } 132 Granularity cleanup = repositoryConfig.cleanup(); 133 granularity = cleanup == Granularity.UNDEFINED ? Granularity.CLASS : cleanup; 134 repositoryFactoryClass = repositoryConfig.repositoryFactoryClass(); 135 } 136 137 public Granularity getGranularity() { 138 return granularity; 139 } 140 141 @Override 142 public void start(FeaturesRunner runner) { 143 try { 144 log.info("Deploying a VCS repo implementation"); 145 // setup system properties for generic XML extension points 146 DatabaseHelper dbHelper = DatabaseHelper.DATABASE; 147 dbHelper.setUp(repositoryFactoryClass); 148 String contribPath = dbHelper.getDeploymentContrib(); 149 RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness(); 150 OSGiAdapter osgi = harness.getOSGiAdapter(); 151 Bundle bundle = osgi.getRegistry().getBundle("org.nuxeo.ecm.core.storage.sql.test"); 152 URL contribURL = bundle.getEntry(contribPath); 153 assertNotNull("deployment contrib " + contribPath + " not found", contribURL); 154 Contribution contrib = new ContributionLocation(getRepositoryName(), contribURL); 155 harness.getContext().deploy(contrib); 156 } catch (Exception e) { 157 throw new NuxeoException(e); 158 } 159 } 160 161 @Override 162 public void beforeRun(FeaturesRunner runner) { 163 // wait for async tasks that may have been triggered by 164 // RuntimeFeature (typically repo initialization) 165 Framework.getLocalService(EventService.class).waitForAsyncCompletion(); 166 final CoreInstance core = CoreInstance.getInstance(); 167 initialOpenSessions = core.getNumberOfSessions(); 168 if (initialOpenSessions != 0) { 169 log.error(String.format("There are already %s open session(s) before running tests.", 170 Integer.valueOf(initialOpenSessions))); 171 for (CoreInstance.RegistrationInfo info : core.getRegistrationInfos()) { 172 log.warn("Leaking session", info); 173 } 174 } 175 if (granularity != Granularity.METHOD) { 176 initializeSession(runner); 177 } 178 } 179 180 @Override 181 public void afterRun(FeaturesRunner runner) { 182 waitForAsyncCompletion(); // fulltext and various workers 183 if (granularity != Granularity.METHOD) { 184 cleanupSession(runner); 185 } 186 if (session != null) { 187 releaseCoreSession(); 188 } 189 190 final CoreInstance core = CoreInstance.getInstance(); 191 int finalOpenSessions = core.getNumberOfSessions(); 192 int leakedOpenSessions = finalOpenSessions - initialOpenSessions; 193 if (leakedOpenSessions > 0) { 194 log.error(String.format("There are %s open session(s) at tear down; it seems " 195 + "the test leaked %s session(s).", Integer.valueOf(finalOpenSessions), 196 Integer.valueOf(leakedOpenSessions))); 197 } 198 } 199 200 @Override 201 public void beforeSetup(FeaturesRunner runner) { 202 if (granularity == Granularity.METHOD) { 203 initializeSession(runner); 204 } 205 } 206 207 @Override 208 public void afterTeardown(FeaturesRunner runner) { 209 if (granularity == Granularity.METHOD) { 210 cleanupSession(runner); 211 } 212 } 213 214 protected void waitForAsyncCompletion() { 215 boolean tx = TransactionHelper.isTransactionActive(); 216 boolean rb = TransactionHelper.isTransactionMarkedRollback(); 217 if (tx || rb) { 218 // there may be afterCommit work pending, so we 219 // have to commit the transaction 220 TransactionHelper.commitOrRollbackTransaction(); 221 } 222 Framework.getLocalService(EventService.class).waitForAsyncCompletion(); 223 if (tx || rb) { 224 // restore previous tx status 225 TransactionHelper.startTransaction(); 226 if (rb) { 227 TransactionHelper.setTransactionRollbackOnly(); 228 } 229 } 230 } 231 232 protected void cleanupSession(FeaturesRunner runner) { 233 waitForAsyncCompletion(); 234 if (TransactionHelper.isTransactionMarkedRollback()) { // ensure tx is 235 // active 236 TransactionHelper.commitOrRollbackTransaction(); 237 TransactionHelper.startTransaction(); 238 } 239 if (session == null) { 240 createCoreSession(); 241 } 242 try { 243 log.trace("remove everything except root"); 244 session.removeChildren(new PathRef("/")); 245 log.trace("remove orphan versions as OrphanVersionRemoverListener is not triggered by CoreSession#removeChildren"); 246 String rootDocumentId = session.getRootDocument().getId(); 247 IterableQueryResult results = session.queryAndFetch("SELECT ecm:uuid FROM Document, Relation", NXQL.NXQL); 248 for (Map<String, Serializable> result : results) { 249 String uuid = result.get("ecm:uuid").toString(); 250 if (rootDocumentId != uuid) { 251 try { 252 session.removeDocument(new IdRef(uuid)); 253 } catch (DocumentNotFoundException e) { 254 // could have unknown type in db, ignore 255 } 256 } 257 } 258 results.close(); 259 session.save(); 260 waitForAsyncCompletion(); 261 if (!session.query("SELECT * FROM Document, Relation").isEmpty()) { 262 log.error("Fail to cleanupSession, repository will not be empty for the next test."); 263 } 264 } catch (NuxeoException e) { 265 log.error("Unable to reset repository", e); 266 } finally { 267 CoreScope.INSTANCE.exit(); 268 } 269 releaseCoreSession(); 270 cleaned = true; 271 CoreInstance.getInstance().cleanupThisThread(); 272 } 273 274 protected void initializeSession(FeaturesRunner runner) { 275 if (cleaned) { 276 // re-trigger application started 277 RepositoryService repositoryService = Framework.getLocalService(RepositoryService.class); 278 repositoryService.applicationStarted(null); 279 cleaned = false; 280 } 281 CoreScope.INSTANCE.enter(); 282 createCoreSession(); 283 if (repositoryInit != null) { 284 repositoryInit.populate(session); 285 session.save(); 286 waitForAsyncCompletion(); 287 } 288 } 289 290 public String getRepositoryName() { 291 return storageConfiguration.getRepositoryName(); 292 } 293 294 public CoreSession openCoreSession(String username) { 295 return CoreInstance.openCoreSession(getRepositoryName(), username); 296 } 297 298 public CoreSession openCoreSession(NuxeoPrincipal principal) { 299 return CoreInstance.openCoreSession(getRepositoryName(), principal); 300 } 301 302 public CoreSession openCoreSession() { 303 return CoreInstance.openCoreSession(getRepositoryName()); 304 } 305 306 public CoreSession openCoreSessionSystem() { 307 return CoreInstance.openCoreSessionSystem(getRepositoryName()); 308 } 309 310 public CoreSession createCoreSession() { 311 UserPrincipal principal = new UserPrincipal("Administrator", new ArrayList<String>(), false, true); 312 session = CoreInstance.openCoreSession(getRepositoryName(), principal); 313 return session; 314 } 315 316 public CoreSession getCoreSession() { 317 return session; 318 } 319 320 public void releaseCoreSession() { 321 session.close(); 322 session = null; 323 } 324 325 public CoreSession reopenCoreSession() { 326 releaseCoreSession(); 327 waitForAsyncCompletion(); 328 // flush JCA cache to acquire a new low-level session 329 NuxeoContainer.resetConnectionManager(); 330 createCoreSession(); 331 return session; 332 } 333 334}