/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.core.test;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.IterableQueryResult;
import org.nuxeo.ecm.core.api.PathRef;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.query.sql.NXQL;
import org.nuxeo.ecm.core.repository.RepositoryService;
import org.nuxeo.ecm.core.test.annotations.BackendType;
import org.nuxeo.ecm.core.test.annotations.Granularity;
import org.nuxeo.ecm.core.test.annotations.RepositoryInit;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.test.runner.Deploy;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.RuntimeFeature;
import org.nuxeo.runtime.test.runner.SimpleFeature;
import org.nuxeo.runtime.transaction.TransactionHelper;
import com.google.inject.Binder;
/**
* The core feature provides deployments needed to have a nuxeo core running.
* Several annotations can be used:
* <ul>
* <li>FIXME
* <li>FIXME
* </ul>
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
@Deploy({ "org.nuxeo.runtime.management", "org.nuxeo.ecm.core.schema",
"org.nuxeo.ecm.core.query", "org.nuxeo.ecm.core.api",
"org.nuxeo.ecm.core.event", "org.nuxeo.ecm.core",
"org.nuxeo.ecm.core.convert", "org.nuxeo.ecm.core.convert.plugins",
"org.nuxeo.ecm.core.storage", "org.nuxeo.ecm.core.storage.sql",
"org.nuxeo.ecm.core.storage.sql.test" })
@Features(RuntimeFeature.class)
public class CoreFeature extends SimpleFeature {
private static final Log log = LogFactory.getLog(CoreFeature.class);
protected int initialOpenSessions;
protected RepositorySettings repository;
protected boolean cleaned;
public RepositorySettings getRepository() {
return repository;
}
public BackendType getBackendType() {
return repository.getBackendType();
}
@Override
public void initialize(FeaturesRunner runner) throws Exception {
repository = new RepositorySettings(runner);
runner.getFeature(RuntimeFeature.class).addServiceProvider(repository);
}
@Override
public void start(FeaturesRunner runner) throws Exception {
repository.initialize();
}
@Override
public void configure(FeaturesRunner runner, Binder binder) {
binder.bind(RepositorySettings.class).toInstance(repository);
}
@Override
public void beforeRun(FeaturesRunner runner) throws Exception {
// wait for async tasks that may have been triggered by
// RuntimeFeature (typically repo initialization)
Framework.getLocalService(EventService.class).waitForAsyncCompletion();
final CoreInstance core = CoreInstance.getInstance();
initialOpenSessions = core.getNumberOfSessions();
if (initialOpenSessions != 0) {
log.error(String.format(
"There are already %s open session(s) before running tests.",
Integer.valueOf(initialOpenSessions)));
for (CoreInstance.RegistrationInfo info : core.getRegistrationInfos()) {
log.warn("Leaking session", info);
}
}
if (repository.getGranularity() != Granularity.METHOD) {
initializeSession(runner);
}
}
@Override
public void afterRun(FeaturesRunner runner) throws Exception {
waitForAsyncCompletion(); // fulltext and various workers
if (repository.getGranularity() != Granularity.METHOD) {
cleanupSession(runner);
}
repository.shutdown();
final CoreInstance core = CoreInstance.getInstance();
int finalOpenSessions = core.getNumberOfSessions();
int leakedOpenSessions = finalOpenSessions - initialOpenSessions;
if (leakedOpenSessions > 0) {
log.error(String.format(
"There are %s open session(s) at tear down; it seems "
+ "the test leaked %s session(s).",
Integer.valueOf(finalOpenSessions),
Integer.valueOf(leakedOpenSessions)));
}
}
@Override
public void beforeSetup(FeaturesRunner runner) throws Exception {
if (repository.getGranularity() == Granularity.METHOD) {
initializeSession(runner);
}
}
@Override
public void afterTeardown(FeaturesRunner runner) throws Exception {
if (repository.getGranularity() == Granularity.METHOD) {
cleanupSession(runner);
}
}
protected void waitForAsyncCompletion() {
boolean tx = TransactionHelper.isTransactionActive();
boolean rb = TransactionHelper.isTransactionMarkedRollback();
if (tx || rb) {
// there may be afterCommit work pending, so we
// have to commit the transaction
TransactionHelper.commitOrRollbackTransaction();
}
Framework.getLocalService(EventService.class).waitForAsyncCompletion();
if (tx || rb) {
// restore previous tx status
TransactionHelper.startTransaction();
if (rb) {
TransactionHelper.setTransactionRollbackOnly();
}
}
}
protected void cleanupSession(FeaturesRunner runner) throws ClientException {
waitForAsyncCompletion();
if (TransactionHelper.isTransactionMarkedRollback()) { // ensure tx is
// active
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
}
CoreSession session = repository.getSession();
if (session == null) {
session = repository.createSession();
}
try {
log.trace("remove everything except root");
session.removeChildren(new PathRef("/"));
log.trace("remove orphan versions as OrphanVersionRemoverListener is not triggered by CoreSession#removeChildren");
String rootDocumentId = session.getRootDocument().getId();
IterableQueryResult results = session.queryAndFetch(
"SELECT ecm:uuid FROM Document", NXQL.NXQL);
for (Map<String, Serializable> result : results) {
String uuid = result.get("ecm:uuid").toString();
if (rootDocumentId != uuid) {
session.removeDocument(new IdRef(uuid));
}
}
results.close();
session.save();
waitForAsyncCompletion();
if (!session.query("SELECT * FROM Document").isEmpty()) {
log.error("Fail to cleanupSession, repository will not be empty for the next test.");
}
} catch (ClientException e) {
log.error("Unable to reset repository", e);
} finally {
CoreScope.INSTANCE.exit();
}
repository.releaseSession();
cleaned = true;
CoreInstance.getInstance().cleanupThisThread();
}
protected void initializeSession(FeaturesRunner runner) throws Exception {
if (cleaned) {
// re-trigger application started
RepositoryService repositoryService = Framework.getLocalService(RepositoryService.class);
repositoryService.applicationStarted(null);
cleaned = false;
}
CoreScope.INSTANCE.enter();
CoreSession session = repository.createSession();
if (session == null) {
throw new AssertionError("Cannot open session");
}
RepositoryInit factory = repository.getInitializer();
if (factory != null) {
factory.populate(session);
session.save();
waitForAsyncCompletion();
}
}
public void setRepositorySettings(RepositorySettings settings) {
repository.importSettings(settings);
}
}