/*
* Copyright (c) 2014 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:
* Florent Guillaume
*/
package org.nuxeo.ecm.core;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore;
import org.junit.Test;
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.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.security.ACE;
import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
import org.nuxeo.ecm.core.api.security.impl.ACPImpl;
import org.nuxeo.ecm.core.query.sql.NXQL;
import org.nuxeo.ecm.core.storage.sql.TXSQLRepositoryTestCase;
import org.nuxeo.runtime.transaction.TransactionHelper;
/**
* Tests read ACLs behavior in a transactional setting.
*/
public class TestSQLRepositoryReadAcls extends TXSQLRepositoryTestCase {
protected static final Log log = LogFactory.getLog(TestSQLRepositoryJTAJCA.class);
@Test
public void testParallelPrepareUserReadAcls() throws Throwable {
doParallelPrepareUserReadAcls(0);
}
// fails on SQL Server sometimes (expected:<1> but was:<0>)
@Ignore
@Test
public void testParallelPrepareUserReadAclsMany() throws Throwable {
for (int i = 0; i < 100; i++) {
log.debug("try " + i);
doParallelPrepareUserReadAcls(i);
}
}
protected void doParallelPrepareUserReadAcls(int i) throws Throwable {
// set ACP on root
ACPImpl acp = new ACPImpl();
ACLImpl acl = new ACLImpl();
String username = "user" + i;
acl.add(new ACE("Administrator", "Everything", true));
acl.add(new ACE(username, "Everything", true));
acp.addACL(acl);
String name = "doc" + i;
DocumentModel doc = session.createDocumentModel("/", name, "File");
doc = session.createDocument(doc);
doc.setACP(acp, true);
session.save();
closeSession();
TransactionHelper.commitOrRollbackTransaction();
CyclicBarrier barrier = new CyclicBarrier(2);
CountDownLatch firstReady = new CountDownLatch(1);
PrepareUserReadAclsJob r1 = new PrepareUserReadAclsJob(name, username,
database.repositoryName, firstReady, barrier);
PrepareUserReadAclsJob r2 = new PrepareUserReadAclsJob(name, username,
database.repositoryName, null, barrier);
Thread t1 = null;
Thread t2 = null;
try {
t1 = new Thread(r1, "t1");
t2 = new Thread(r2, "t2");
t1.start();
if (firstReady.await(60, TimeUnit.SECONDS)) {
t2.start();
t1.join();
t1 = null;
t2.join();
t2 = null;
if (r1.throwable != null) {
throw r1.throwable;
}
if (r2.throwable != null) {
throw r2.throwable;
}
} // else timed out
} finally {
// error condition recovery
if (t1 != null) {
t1.interrupt();
}
if (t2 != null) {
t2.interrupt();
}
}
// after both threads have run, check that we don't see
// duplicate documents
TransactionHelper.startTransaction();
session = openSessionAs(username);
checkOneDoc(session, name); // failed for PostgreSQL
closeSession();
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
openSession();
}
protected static void checkOneDoc(CoreSession session, String name)
throws ClientException {
String query = "SELECT * FROM File WHERE ecm:isProxy = 0 AND ecm:name = '"
+ name + "'";
DocumentModelList res = session.query(query, NXQL.NXQL, null, 0, 0,
false);
assertEquals(1, res.size());
}
protected static class PrepareUserReadAclsJob implements Runnable {
private String name;
private String username;
private String repositoryName;
public CountDownLatch ready;
public CyclicBarrier barrier;
public Throwable throwable;
public PrepareUserReadAclsJob(String name, String username,
String repositoryName, CountDownLatch ready,
CyclicBarrier barrier) {
this.name = name;
this.username = username;
this.repositoryName = repositoryName;
this.ready = ready;
this.barrier = barrier;
}
@Override
public void run() {
TransactionHelper.startTransaction();
try (CoreSession session = CoreInstance.openCoreSession(
repositoryName, username)) {
if (ready != null) {
ready.countDown();
ready = null;
}
barrier.await(30, TimeUnit.SECONDS); // (throws on timeout)
barrier = null;
checkOneDoc(session, name); // fails for Oracle
} catch (Throwable t) {
t.printStackTrace();
throwable = t;
} finally {
TransactionHelper.commitOrRollbackTransaction();
// error recovery
// still count down as main thread is awaiting us
if (ready != null) {
ready.countDown();
}
// break barrier for other thread
if (barrier != null) {
barrier.reset(); // break barrier
}
}
}
}
}