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