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