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 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 getStorageConfiguration().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}