/*
* (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.ecm.core.api.Blobs;
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.DocumentRef;
import org.nuxeo.ecm.core.api.DocumentSecurityException;
import org.nuxeo.ecm.core.api.PathRef;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.VersionModel;
import org.nuxeo.ecm.core.api.VersioningOption;
import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
import org.nuxeo.ecm.core.api.impl.blob.StringBlob;
import org.nuxeo.ecm.core.api.security.ACE;
import org.nuxeo.ecm.core.api.security.ACL;
import org.nuxeo.ecm.core.api.security.ACP;
import org.nuxeo.ecm.core.api.security.SecurityConstants;
import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
import org.nuxeo.ecm.core.api.security.impl.ACPImpl;
import org.nuxeo.ecm.core.schema.FacetNames;
import org.nuxeo.ecm.core.storage.sql.listeners.DummyUpdateBeforeModificationListener;
import org.nuxeo.ecm.core.test.CoreFeature;
import org.nuxeo.ecm.core.test.annotations.Granularity;
import org.nuxeo.ecm.core.test.annotations.RepositoryConfig;
import org.nuxeo.ecm.core.versioning.VersioningService;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.LocalDeploy;
import org.nuxeo.runtime.transaction.TransactionHelper;
@RunWith(FeaturesRunner.class)
@Features(CoreFeature.class)
@RepositoryConfig(cleanup = Granularity.METHOD)
@LocalDeploy({ "org.nuxeo.ecm.core.test.tests:OSGI-INF/test-repo-core-types-contrib.xml",
"org.nuxeo.ecm.core.test.tests:OSGI-INF/test-listener-beforemod-updatedoc-contrib.xml" })
public class TestSQLRepositoryVersioning {
@Inject
protected CoreFeature coreFeature;
@Inject
protected CoreSession session;
protected CoreSession openSessionAs(String username) {
return CoreInstance.openCoreSession(session.getRepositoryName(), username);
}
/**
* Sleep 1s, useful for stupid databases (like MySQL) that don't have subsecond resolution in TIMESTAMP fields.
*/
protected void maybeSleepToNextSecond() {
coreFeature.getStorageConfiguration().maybeSleepToNextSecond();
}
protected void waitForFulltextIndexing() {
nextTransaction();
coreFeature.getStorageConfiguration().waitForFulltextIndexing();
}
protected void nextTransaction() {
if (TransactionHelper.isTransactionActiveOrMarkedRollback()) {
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
}
}
@Test
public void testCreateVersionsManyTimes() throws Exception {
for (int i = 0; i < 10; i++) {
createVersions(i);
}
}
protected void createVersions(int i) throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "fold" + i, "Folder");
session.createDocument(folder);
DocumentModel file = new DocumentModelImpl("/fold" + i, "file", "File");
file = session.createDocument(file);
createTrioVersions(file);
}
@Test
public void testRemoveSingleDocVersion() throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "folder#1", "Folder");
folder = session.createDocument(folder);
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(), "file#1", "File");
file = session.createDocument(file);
checkVersions(file);
file.setPropertyValue("file:content", (Serializable) Blobs.createBlob("B", "text/plain", "UTF-8", "A"));
file = session.saveDocument(file);
file.checkIn(VersioningOption.MINOR, null);
file.checkOut(); // to allow deleting last version
checkVersions(file, "0.1");
DocumentModel lastversion = session.getLastDocumentVersion(file.getRef());
assertNotNull(lastversion);
assertTrue(lastversion.isVersion());
session.removeDocument(lastversion.getRef());
checkVersions(file);
}
// Creates 3 versions and removes the first.
@Test
public void testRemoveFirstDocVersion() throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "folder#1", "Folder");
folder = session.createDocument(folder);
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(), "file#1", "File");
file = session.createDocument(file);
createTrioVersions(file);
final int VERSION_INDEX = 0;
DocumentModel firstversion = session.getVersions(file.getRef()).get(VERSION_INDEX);
assertNotNull(firstversion);
assertTrue(firstversion.isVersion());
session.removeDocument(firstversion.getRef());
checkVersions(file, "0.2", "0.3");
}
// Creates 3 versions and removes the second.
@Test
public void testRemoveMiddleDocVersion() throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "folder#1", "Folder");
folder = session.createDocument(folder);
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(), "file#1", "File");
file = session.createDocument(file);
createTrioVersions(file);
final int VERSION_INDEX = 1;
DocumentModel version = session.getVersions(file.getRef()).get(VERSION_INDEX);
assertNotNull(version);
assertTrue(version.isVersion());
session.removeDocument(version.getRef());
checkVersions(file, "0.1", "0.3");
}
// Creates 3 versions and removes the last.
@Test
public void testRemoveLastDocVersion() throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "folder#1", "Folder");
folder = session.createDocument(folder);
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(), "file#1", "File");
file = session.createDocument(file);
createTrioVersions(file);
final int VERSION_INDEX = 2;
DocumentModel lastversion = session.getVersions(file.getRef()).get(VERSION_INDEX);
assertNotNull(lastversion);
assertTrue(lastversion.isVersion());
session.removeDocument(lastversion.getRef());
checkVersions(file, "0.1", "0.2");
}
private void createTrioVersions(DocumentModel file) throws Exception {
// create a first version
file.setProperty("file", "content", new StringBlob("A"));
file = session.saveDocument(file);
file.checkIn(VersioningOption.MINOR, null);
checkVersions(file, "0.1");
// create a second version
// make it dirty so it will be saved
file.setProperty("file", "content", new StringBlob("B"));
maybeSleepToNextSecond();
file = session.saveDocument(file);
file.checkIn(VersioningOption.MINOR, null);
checkVersions(file, "0.1", "0.2");
// create a third version
file.setProperty("file", "content", new StringBlob("C"));
maybeSleepToNextSecond();
file = session.saveDocument(file);
file.checkIn(VersioningOption.MINOR, null);
file.checkOut(); // to allow deleting last version
checkVersions(file, "0.1", "0.2", "0.3");
}
private void checkVersions(DocumentModel doc, String... labels) {
List<String> actual = new LinkedList<String>();
for (DocumentModel ver : session.getVersions(doc.getRef())) {
assertTrue(ver.isVersion());
actual.add(ver.getVersionLabel());
}
// build a debug list of versions and creation times
// in case of failure
StringBuilder buf = new StringBuilder("version time: ");
for (VersionModel vm : session.getVersionsForDocument(doc.getRef())) {
buf.append(vm.getLabel());
buf.append("=");
buf.append(vm.getCreated().getTimeInMillis());
buf.append(", ");
}
buf.setLength(buf.length() - 2);
assertEquals(buf.toString(), Arrays.asList(labels), actual);
List<DocumentRef> versionsRefs = session.getVersionsRefs(doc.getRef());
assertEquals(labels.length, versionsRefs.size());
}
@Test
public void testCheckInCheckOut() throws Exception {
DocumentModel doc = new DocumentModelImpl("/", "file#789", "File");
assertTrue(doc.isCheckedOut());
doc = session.createDocument(doc);
assertTrue(session.isCheckedOut(doc.getRef()));
assertTrue(doc.isCheckedOut());
session.save();
assertTrue(session.isCheckedOut(doc.getRef()));
assertTrue(doc.isCheckedOut());
DocumentRef verRef = session.checkIn(doc.getRef(), null, null);
DocumentModel ver = session.getDocument(verRef);
assertTrue(ver.isVersion());
doc.refresh();
assertFalse(session.isCheckedOut(doc.getRef()));
assertFalse(doc.isCheckedOut());
session.checkOut(doc.getRef());
assertTrue(session.isCheckedOut(doc.getRef()));
// using DocumentModel API
DocumentRef verRef2 = doc.checkIn(null, null);
DocumentModel ver2 = session.getDocument(verRef2);
assertTrue(ver2.isVersion());
assertFalse(doc.isCheckedOut());
doc.checkOut();
assertTrue(doc.isCheckedOut());
}
@Test
public void testAutoCheckOut() throws Exception {
DocumentModel doc = new DocumentModelImpl("/", "file", "File");
doc.setPropertyValue("dc:title", "t0");
doc = session.createDocument(doc);
assertTrue(doc.isCheckedOut());
session.checkIn(doc.getRef(), null, null);
doc.refresh();
assertFalse(doc.isCheckedOut());
// auto-checkout
doc.setPropertyValue("dc:title", "t1");
doc = session.saveDocument(doc);
assertTrue(doc.isCheckedOut());
session.checkIn(doc.getRef(), null, null);
doc.refresh();
assertFalse(doc.isCheckedOut());
// disable auto-checkout
doc.setPropertyValue("dc:title", "t2");
doc.putContextData(VersioningService.DISABLE_AUTO_CHECKOUT, Boolean.TRUE);
doc = session.saveDocument(doc);
assertFalse(doc.isCheckedOut());
assertEquals("t2", doc.getPropertyValue("dc:title"));
// can still be checked out normally afterwards
doc.checkOut();
assertTrue(doc.isCheckedOut());
assertEquals("t2", doc.getPropertyValue("dc:title"));
}
@Test
public void testRestoreToVersion() throws Exception {
String name2 = "file#456";
DocumentModel doc = new DocumentModelImpl("/", name2, "File");
doc = session.createDocument(doc);
DocumentRef docRef = doc.getRef();
session.save();
DocumentRef v1Ref = session.checkIn(docRef, null, null);
assertFalse(session.isCheckedOut(docRef));
session.checkOut(docRef);
assertTrue(session.isCheckedOut(docRef));
doc.setProperty("dublincore", "title", "f1");
doc.setProperty("dublincore", "description", "desc 1");
session.saveDocument(doc);
session.save();
maybeSleepToNextSecond();
DocumentRef v2Ref = session.checkIn(docRef, null, null);
session.checkOut(docRef);
DocumentModel newDoc = session.getDocument(docRef);
assertNotNull(newDoc);
assertNotNull(newDoc.getRef());
assertEquals("desc 1", newDoc.getProperty("dublincore", "description"));
waitForFulltextIndexing();
maybeSleepToNextSecond();
DocumentModel restoredDoc = session.restoreToVersion(docRef, v1Ref);
assertNotNull(restoredDoc);
assertNotNull(restoredDoc.getRef());
assertNull(restoredDoc.getProperty("dublincore", "description"));
waitForFulltextIndexing();
maybeSleepToNextSecond();
restoredDoc = session.restoreToVersion(docRef, v2Ref);
assertNotNull(restoredDoc);
assertNotNull(restoredDoc.getRef());
assertEquals("desc 1", restoredDoc.getProperty("dublincore", "description"));
}
@Test
public void testRestoreInvalidations() throws Exception {
DocumentModel doc = new DocumentModelImpl("/", "myfile", "File");
doc.setPropertyValue("dc:title", "t1");
doc = session.createDocument(doc);
final DocumentRef docRef = doc.getRef();
DocumentRef v1 = session.checkIn(docRef, null, null);
session.checkOut(docRef);
doc.setPropertyValue("dc:title", "t2");
session.saveDocument(doc);
session.save();
waitForFulltextIndexing();
// we need 2 threads to get 2 different sessions that send each other invalidations
final CyclicBarrier barrier = new CyclicBarrier(2);
Throwable[] throwables = new Throwable[2];
Thread t1 = new Thread() {
@Override
public void run() {
TransactionHelper.startTransaction();
try (CoreSession session = openSessionAs(SecurityConstants.ADMINISTRATOR)) {
DocumentModel doc = session.getDocument(docRef);
assertEquals("t2", doc.getPropertyValue("dc:title"));
// 1. sync
barrier.await(30, TimeUnit.SECONDS); // (throws on timeout)
// 2. restore and next tx to send invalidations
DocumentModel restored = session.restoreToVersion(docRef, v1);
assertEquals("t1", restored.getPropertyValue("dc:title"));
session.save();
nextTransaction();
// 3. sync
barrier.await(30, TimeUnit.SECONDS); // (throws on timeout)
// 4. wait
} catch (InterruptedException | BrokenBarrierException | TimeoutException | RuntimeException
| AssertionError t) {
throwables[0] = t;
} finally {
TransactionHelper.commitOrRollbackTransaction();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
TransactionHelper.startTransaction();
try (CoreSession session = openSessionAs(SecurityConstants.ADMINISTRATOR)) {
DocumentModel doc = session.getDocument(docRef);
assertEquals("t2", doc.getPropertyValue("dc:title"));
// 1. sync
barrier.await(30, TimeUnit.SECONDS); // (throws on timeout)
// 2. nop
// 3. sync
barrier.await(30, TimeUnit.SECONDS); // (throws on timeout)
// 4. next tx to get invalidations and check
nextTransaction();
DocumentModel restored = session.getDocument(docRef);
assertEquals("t1", restored.getPropertyValue("dc:title"));
} catch (InterruptedException | BrokenBarrierException | TimeoutException | RuntimeException
| AssertionError t) {
throwables[1] = t;
} finally {
TransactionHelper.commitOrRollbackTransaction();
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
AssertionError assertionError = null;
for (Throwable t : throwables) {
if (t != null) {
if (assertionError == null) {
assertionError = new AssertionError("Exceptions in threads");
}
assertionError.addSuppressed(t);
}
}
if (assertionError != null) {
throw assertionError;
}
}
@Test
public void testGetDocumentWithVersion() throws Exception {
String name2 = "file#248";
DocumentModel childFile = new DocumentModelImpl("/", name2, "File");
childFile = session.createDocument(childFile);
session.save();
DocumentRef v1Ref = session.checkIn(childFile.getRef(), null, null);
session.checkOut(childFile.getRef());
childFile.setProperty("dublincore", "title", "f1");
childFile.setProperty("dublincore", "description", "desc 1");
session.saveDocument(childFile);
session.save();
maybeSleepToNextSecond();
DocumentRef v2Ref = session.checkIn(childFile.getRef(), null, null);
DocumentModel newDoc = session.getDocument(childFile.getRef());
assertNotNull(newDoc);
assertNotNull(newDoc.getRef());
assertEquals("desc 1", newDoc.getProperty("dublincore", "description"));
// restore, no snapshot as already pristine
waitForFulltextIndexing();
maybeSleepToNextSecond();
DocumentModel restoredDoc = session.restoreToVersion(childFile.getRef(), v1Ref);
assertNotNull(restoredDoc);
assertNotNull(restoredDoc.getRef());
assertNull(restoredDoc.getProperty("dublincore", "description"));
DocumentModel last = session.getLastDocumentVersion(childFile.getRef());
assertNotNull(last);
assertNotNull(last.getRef());
assertEquals(v2Ref.reference(), last.getId());
assertEquals("desc 1", last.getProperty("dublincore", "description"));
}
// security on versions, see TestLocalAPIWithCustomVersioning
@Test
public void testVersionSecurity() throws Exception {
DocumentModel folder = new DocumentModelImpl("/", "folder", "Folder");
folder = session.createDocument(folder);
ACP acp = new ACPImpl();
ACE ace = new ACE("princ1", "perm1", true);
ACL acl = new ACLImpl("acl1", false);
acl.add(ace);
acp.addACL(acl);
session.setACP(folder.getRef(), acp, true);
DocumentModel file = new DocumentModelImpl("/folder", "file", "File");
file = session.createDocument(file);
// set security
acp = new ACPImpl();
ace = new ACE("princ2", "perm2", true);
acl = new ACLImpl("acl2", false);
acl.add(ace);
acp.addACL(acl);
session.setACP(file.getRef(), acp, true);
session.save();
DocumentModel proxy = session.publishDocument(file, folder);
DocumentModel version = session.getLastDocumentVersion(file.getRef());
session.save();
// check security on version
acp = session.getACP(version.getRef());
ACL[] acls = acp.getACLs();
assertEquals(2, acls.length);
acl = acls[0];
assertEquals(1, acl.size());
assertEquals("princ2", acl.get(0).getUsername());
acl = acls[1];
assertEquals(1 + 3, acl.size()); // 1 + 3 root defaults
assertEquals("princ1", acl.get(0).getUsername());
// remove live document (there's a proxy so the version stays)
session.removeDocument(file.getRef());
session.save();
// recheck security on version (works because we're administrator)
acp = session.getACP(version.getRef());
assertNull(acp);
// check proxy still accessible (in another session)
try (CoreSession session2 = openSessionAs(SecurityConstants.ADMINISTRATOR)) {
session2.getDocument(proxy.getRef());
}
}
@Test
public void testVersionRemoval() throws Exception {
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel file = session.createDocumentModel("/folder", "file", "File");
file = session.createDocument(file);
DocumentModel proxy = session.publishDocument(file, folder);
DocumentModel version = session.getLastDocumentVersion(file.getRef());
session.save();
// even Administrator/system cannot remove a version with a proxy
try {
session.removeDocument(version.getRef());
fail("Admin should not be able to remove version");
} catch (DocumentSecurityException e) {
// ok
}
// remove the proxy first
session.removeDocument(proxy.getRef());
session.save();
// check out the working copy
file.checkOut();
// we can now remove the version
session.removeDocument(version.getRef());
session.save();
}
@Test
public void testVersionLifecycle() throws Exception {
DocumentModel root = session.getRootDocument();
DocumentModel doc = new DocumentModelImpl("/", "doc", "File");
doc = session.createDocument(doc);
doc.setProperty("dublincore", "title", "t1");
doc = session.saveDocument(doc);
session.publishDocument(doc, root);
session.save();
// get version
DocumentModel ver = session.getLastDocumentVersion(doc.getRef());
assertTrue(ver.isVersion());
assertEquals("project", ver.getCurrentLifeCycleState());
ver.followTransition("approve");
session.save();
doc = session.getDocument(new PathRef("/doc"));
ver = session.getLastDocumentVersion(doc.getRef());
assertEquals("approved", ver.getCurrentLifeCycleState());
}
@Test
public void testTransitionProxy() throws Exception {
DocumentModel root = session.getRootDocument();
DocumentModel doc = new DocumentModelImpl("/", "doc", "File");
doc = session.createDocument(doc);
doc.setProperty("dublincore", "title", "t1");
doc = session.saveDocument(doc);
DocumentModel proxy = session.publishDocument(doc, root);
session.save();
Collection<String> transitions = proxy.getAllowedStateTransitions();
assertEquals(3, transitions.size());
if (proxy.getAllowedStateTransitions().contains("delete")) {
proxy.followTransition("delete");
}
assertEquals("deleted", proxy.getCurrentLifeCycleState());
}
@Test
public void testCopy() {
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
session.save();
String versionSeriesId = doc.getVersionSeriesId();
// copy
DocumentModel copy = session.copy(doc.getRef(), session.getRootDocument().getRef(), "fileCopied");
// check different version series id
assertNotSame(versionSeriesId, copy.getVersionSeriesId());
// create version and proxy
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel proxy = session.publishDocument(doc, folder);
// check same version series id
assertEquals(versionSeriesId, proxy.getVersionSeriesId());
// copy proxy
DocumentModel proxyCopy = session.copy(proxy.getRef(), session.getRootDocument().getRef(), "proxyCopied");
// check same version series id
assertEquals(versionSeriesId, proxyCopy.getVersionSeriesId());
}
@Test
public void testCopyCheckedIn() {
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
doc.checkIn(VersioningOption.MAJOR, "comment");
session.save();
assertFalse(doc.isCheckedOut());
assertEquals("1.0", doc.getVersionLabel());
// copy
DocumentModel copy = session.copy(doc.getRef(), session.getRootDocument().getRef(), "fileCopied");
assertTrue(copy.isCheckedOut());
assertEquals("0.0", copy.getVersionLabel());
}
@Test
public void testVersionLabelOfCopy() {
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
session.save();
DocumentModel copy = session.copy(doc.getRef(), new PathRef("/"), "copy");
DocumentRef versionRef = session.checkIn(copy.getRef(), VersioningOption.MINOR, null);
DocumentModel version = session.getDocument(versionRef);
// check version label
assertEquals("0.1", version.getVersionLabel());
}
@Test
public void testPublishing() {
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
checkVersions(doc);
// publish
DocumentModel proxy = session.publishDocument(doc, folder);
session.save();
String versionSeriesId = doc.getVersionSeriesId();
assertFalse(proxy.isVersion());
assertTrue(proxy.isProxy());
assertTrue(proxy.hasFacet(FacetNames.IMMUTABLE));
assertTrue(proxy.isImmutable());
assertEquals(versionSeriesId, proxy.getVersionSeriesId());
assertNotSame(versionSeriesId, proxy.getId());
assertEquals("0.1", proxy.getVersionLabel());
assertNull(proxy.getCheckinComment());
assertFalse(proxy.isMajorVersion());
assertTrue(proxy.isLatestVersion());
assertFalse(proxy.isLatestMajorVersion());
checkVersions(doc, "0.1");
DocumentModel lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNotNull(lastVersionDocument);
assertEquals("file", lastVersionDocument.getName());
assertEquals("0.1", lastVersionDocument.getVersionLabel());
}
@Test
public void testPublishingAfterVersionDelete() {
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
checkVersions(doc);
DocumentModel lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNull(lastVersionDocument);
// publish
DocumentModel proxy = session.publishDocument(doc, folder);
checkVersions(doc, "0.1");
lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNotNull(lastVersionDocument);
assertEquals("file", lastVersionDocument.getName());
assertEquals("0.1", lastVersionDocument.getVersionLabel());
// unpublish
session.removeDocument(proxy.getRef());
// delete the version
List<VersionModel> versions = session.getVersionsForDocument(doc.getRef());
assertEquals(1, versions.size());
DocumentModel docVersion = session.getDocumentWithVersion(doc.getRef(), versions.get(0));
// check out the working copy to remove the base version
doc.checkOut();
session.removeDocument(docVersion.getRef());
checkVersions(doc);
lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNull(lastVersionDocument);
// republish
DocumentModel newProxy = session.publishDocument(doc, folder);
checkVersions(doc, "0.2");
lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNotNull(lastVersionDocument);
assertEquals("file", lastVersionDocument.getName());
assertEquals("0.2", lastVersionDocument.getVersionLabel());
}
@Test
public void testPublishingAfterCopy() {
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel doc = session.createDocumentModel("/", "file", "File");
doc = session.createDocument(doc);
checkVersions(doc);
// publish
DocumentModel proxy = session.publishDocument(doc, folder);
checkVersions(doc, "0.1");
DocumentModel lastVersion = session.getLastDocumentVersion(doc.getRef());
assertNotNull(lastVersion);
assertEquals("0.1", lastVersion.getVersionLabel());
DocumentModel lastVersionDocument = session.getLastDocumentVersion(doc.getRef());
assertNotNull(lastVersionDocument);
assertEquals("file", lastVersionDocument.getName());
// copy published file, version is reset
DocumentModel copy = session.copy(doc.getRef(), folder.getRef(), "fileCopied");
checkVersions(copy);
lastVersion = session.getLastDocumentVersion(copy.getRef());
assertNull(lastVersion);
lastVersionDocument = session.getLastDocumentVersion(copy.getRef());
assertNull(lastVersionDocument);
// republish
DocumentModel newProxy = session.publishDocument(copy, folder);
checkVersions(copy, "0.1");
lastVersion = session.getLastDocumentVersion(copy.getRef());
assertNotNull(lastVersion);
assertEquals("0.1", lastVersion.getVersionLabel());
lastVersionDocument = session.getLastDocumentVersion(copy.getRef());
assertNotNull(lastVersionDocument);
assertEquals("fileCopied", lastVersionDocument.getName());
}
@Test
public void testCmisProperties() throws Exception {
/*
* checked out doc (live; private working copy)
*/
DocumentModel doc = new DocumentModelImpl("/", "myfile", "File");
doc = session.createDocument(doc);
assertTrue(doc.isCheckedOut()); // nuxeo prop, false only on live
assertFalse(doc.isVersion());
assertFalse(doc.isProxy());
assertFalse(doc.hasFacet(FacetNames.IMMUTABLE));
assertFalse(doc.isImmutable());
String versionSeriesId = doc.getVersionSeriesId();
assertNotNull(versionSeriesId);
// assertNotSame(versionSeriesId, doc.getId());
assertEquals("0.0", doc.getVersionLabel());
assertNull(doc.getCheckinComment());
assertFalse(doc.isMajorVersion());
assertFalse(doc.isLatestVersion());
assertFalse(doc.isLatestMajorVersion());
assertTrue(doc.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(doc.getRef()).getId());
/*
* proxy to checked out doc (live proxy)
*/
DocumentModel proxy = session.createProxy(doc.getRef(), session.getRootDocument().getRef());
assertTrue(proxy.isCheckedOut()); // nuxeo prop, false only on live
assertFalse(proxy.isVersion());
assertTrue(proxy.isProxy());
assertFalse(proxy.hasFacet(FacetNames.IMMUTABLE));
assertFalse(proxy.isImmutable());
assertEquals(versionSeriesId, proxy.getVersionSeriesId());
assertEquals("0.0", proxy.getVersionLabel());
assertNull(proxy.getCheckinComment());
assertFalse(proxy.isMajorVersion());
assertFalse(proxy.isLatestVersion());
assertFalse(proxy.isLatestMajorVersion());
assertTrue(proxy.isVersionSeriesCheckedOut());
assertTrue(doc.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(proxy.getRef()).getId());
/*
* checked in doc
*/
DocumentRef verRef = doc.checkIn(VersioningOption.MINOR, "comment");
session.save();
DocumentModel ver = session.getDocument(verRef);
proxy.refresh();
assertFalse(doc.isCheckedOut());
assertFalse(doc.isVersion());
assertFalse(doc.isProxy());
// assertTrue(doc.hasFacet(FacetNames.IMMUTABLE)); // debatable
// assertTrue(doc.isImmutable()); // debatable
assertEquals(versionSeriesId, doc.getVersionSeriesId());
assertEquals("0.1", doc.getVersionLabel());
assertNull(doc.getCheckinComment());
assertFalse(doc.isMajorVersion());
assertFalse(doc.isLatestVersion());
assertFalse(doc.isLatestMajorVersion());
assertFalse(doc.isVersionSeriesCheckedOut());
assertFalse(proxy.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(proxy.getRef()).getId());
// TODO proxy to checked in doc
/*
* version
*/
// assertFalse(ver.isCheckedOut()); // TODO
assertTrue(ver.isVersion());
assertFalse(ver.isProxy());
assertTrue(ver.hasFacet(FacetNames.IMMUTABLE));
assertTrue(ver.isImmutable());
assertEquals(versionSeriesId, ver.getVersionSeriesId());
assertEquals("0.1", ver.getVersionLabel());
assertEquals("comment", ver.getCheckinComment());
assertFalse(ver.isMajorVersion());
assertTrue(ver.isLatestVersion());
assertFalse(ver.isLatestMajorVersion());
assertFalse(ver.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(ver.getRef()).getId());
/*
* proxy to version
*/
proxy = session.createProxy(ver.getRef(), session.getRootDocument().getRef());
assertFalse(proxy.isCheckedOut());
assertFalse(proxy.isVersion());
assertTrue(proxy.isProxy());
assertTrue(proxy.hasFacet(FacetNames.IMMUTABLE));
assertTrue(proxy.isImmutable());
assertEquals(versionSeriesId, proxy.getVersionSeriesId());
assertEquals("0.1", proxy.getVersionLabel());
assertEquals("comment", proxy.getCheckinComment());
assertFalse(proxy.isMajorVersion());
assertTrue(proxy.isLatestVersion());
assertFalse(proxy.isLatestMajorVersion());
assertFalse(proxy.isVersionSeriesCheckedOut());
assertFalse(doc.isVersionSeriesCheckedOut());
assertFalse(ver.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(proxy.getRef()).getId());
/*
* re-checked out doc
*/
doc.checkOut();
ver.refresh();
proxy.refresh();
assertTrue(doc.isCheckedOut());
assertFalse(doc.isVersion());
assertFalse(doc.isProxy());
assertFalse(doc.hasFacet(FacetNames.IMMUTABLE));
assertFalse(doc.isImmutable());
assertEquals(versionSeriesId, doc.getVersionSeriesId());
assertEquals("0.1+", doc.getVersionLabel());
assertNull(doc.getCheckinComment());
assertFalse(doc.isMajorVersion());
assertFalse(doc.isLatestVersion());
assertFalse(doc.isLatestMajorVersion());
assertTrue(doc.isVersionSeriesCheckedOut());
assertTrue(ver.isVersionSeriesCheckedOut());
assertTrue(proxy.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(doc.getRef()).getId());
/*
* major checkin
*/
DocumentRef majRef = doc.checkIn(VersioningOption.MAJOR, "yo");
DocumentModel maj = session.getDocument(majRef);
ver.refresh();
proxy.refresh();
assertTrue(maj.isMajorVersion());
assertTrue(maj.isLatestVersion());
assertTrue(maj.isLatestMajorVersion());
assertFalse(maj.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(maj.getRef()).getId());
// previous ver
assertFalse(ver.isMajorVersion());
assertFalse(ver.isLatestVersion());
assertFalse(ver.isLatestMajorVersion());
assertFalse(ver.isVersionSeriesCheckedOut());
assertFalse(doc.isVersionSeriesCheckedOut());
assertFalse(proxy.isVersionSeriesCheckedOut());
assertEquals(doc.getId(), session.getWorkingCopy(ver.getRef()).getId());
}
@Test
public void testSaveRestoredVersionWithVersionAutoIncrement() {
// check-in version 1.0, 2.0 and restore version 1.0
DocumentModel doc = new DocumentModelImpl("/", "myfile", "File");
doc = session.createDocument(doc);
doc = session.saveDocument(doc);
DocumentRef co = doc.getRef();
DocumentRef ci1 = session.checkIn(co, VersioningOption.MAJOR, "first check-in");
session.checkOut(co);
maybeSleepToNextSecond();
DocumentRef ci2 = session.checkIn(co, VersioningOption.MAJOR, "second check-in");
waitForFulltextIndexing();
maybeSleepToNextSecond();
session.restoreToVersion(co, ci1);
// save document with auto-increment should produce version 3.0
doc = session.getDocument(co);
assertEquals(doc.getVersionLabel(), "1.0");
doc.putContextData(VersioningService.VERSIONING_OPTION, VersioningOption.MAJOR);
// mark as dirty - must change the value
doc.setPropertyValue("dc:title", doc.getPropertyValue("dc:title") + " dirty");
doc = session.saveDocument(doc);
assertEquals(doc.getVersionLabel(), "3.0");
}
@Test
public void testAllowVersionWrite() {
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc.setPropertyValue("icon", "icon1");
doc = session.createDocument(doc);
DocumentRef verRef = session.checkIn(doc.getRef(), null, null);
// regular version cannot be written
DocumentModel ver = session.getDocument(verRef);
ver.setPropertyValue("icon", "icon2");
try {
session.saveDocument(ver);
fail("Should not allow version write");
} catch (PropertyException e) {
assertTrue(e.getMessage(), e.getMessage().contains("Cannot set property on a version"));
}
// with proper option, it's allowed
ver.setPropertyValue("icon", "icon3");
ver.putContextData(CoreSession.ALLOW_VERSION_WRITE, Boolean.TRUE);
session.saveDocument(ver);
// refetch to check
ver = session.getDocument(verRef);
assertEquals("icon3", ver.getPropertyValue("icon"));
}
@Test
public void testAllowVersionWriteACL() {
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc = session.createDocument(doc);
DocumentRef verRef = session.checkIn(doc.getRef(), null, null);
DocumentModel ver = session.getDocument(verRef);
ACL acl = new ACLImpl("acl1", false);
ACE ace = new ACE("princ1", "perm1", true);
acl.add(ace);
ACP acp = new ACPImpl();
acp.addACL(acl);
// check that ACP can be set
ver.setACP(acp, true);
}
@Test
public void testGetLastVersion() {
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc = session.createDocument(doc);
session.save();
DocumentRef v1ref = session.checkIn(doc.getRef(), VersioningOption.MAJOR, null);
session.checkOut(doc.getRef());
maybeSleepToNextSecond();
DocumentRef v2ref = session.checkIn(doc.getRef(), VersioningOption.MINOR, null);
// last version on the doc
DocumentModel last = session.getLastDocumentVersion(doc.getRef());
assertEquals(v2ref.reference(), last.getId());
DocumentRef lastRef = session.getLastDocumentVersionRef(doc.getRef());
assertEquals(v2ref.reference(), lastRef.reference());
// last version on any version
last = session.getLastDocumentVersion(v2ref);
assertEquals(v2ref.reference(), last.getId());
lastRef = session.getLastDocumentVersionRef(v2ref);
assertEquals(v2ref.reference(), lastRef.reference());
}
@Test
public void testGetVersions() {
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc = session.createDocument(doc);
session.save();
DocumentRef v1ref = session.checkIn(doc.getRef(), VersioningOption.MAJOR, null);
session.checkOut(doc.getRef());
session.checkIn(doc.getRef(), VersioningOption.MINOR, null);
// versions on the doc
List<DocumentModel> vers = session.getVersions(doc.getRef());
assertEquals(2, vers.size());
List<DocumentRef> verRefs = session.getVersionsRefs(doc.getRef());
assertEquals(2, verRefs.size());
// versions on any version
vers = session.getVersions(v1ref);
assertEquals(2, vers.size());
verRefs = session.getVersionsRefs(v1ref);
assertEquals(2, verRefs.size());
}
@Test
public void testSearchVersionWithNoLiveDocument() {
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc = session.createDocument(doc);
session.save();
// create a version
DocumentRef vref = session.checkIn(doc.getRef(), VersioningOption.MAJOR, null);
// create a proxy
session.createProxy(vref, new PathRef("/"));
// remove the live doc, the version is not removed because of the proxy
session.removeDocument(doc.getRef());
session.save();
// now search as non-admin
try (CoreSession bobSession = openSessionAs("bob")) {
// if this returns then all is well, otherwise it means there's an infinite loop somewhere
bobSession.query("SELECT * FROM Document");
}
}
@Test
public void testRemoveLiveProxyTarget() {
DocumentModel fold = session.createDocumentModel("/", "fold", "Folder");
fold = session.createDocument(fold);
DocumentModel doc = session.createDocumentModel("/fold", "doc", "File");
doc = session.createDocument(doc);
// create a live proxy to the doc
// put proxy in same folder so that we can remove both at once
session.createProxy(doc.getRef(), fold.getRef());
session.save();
// remove the folder, containing the doc which is a proxy target
session.removeDocument(fold.getRef());
session.save();
}
@Test
public void testDirtyStateBehaviours() throws Exception {
// given a created doc with a given version
DocumentModel doc = session.createDocumentModel("/", "doc", "File");
doc = session.createDocument(doc);
session.checkIn(doc.getRef(), VersioningOption.MAJOR, "Increment major version");
doc = session.getDocument(doc.getRef());
String v1 = doc.getVersionLabel();
// when I update the doc with no change
doc = session.saveDocument(doc);
// then the version doesnt change
doc = session.getDocument(doc.getRef());
assertEquals(v1, doc.getVersionLabel());
// when I update the doc with changes
doc.setPropertyValue("dc:title", "coucou");
doc = session.saveDocument(doc);
// then the version change
doc = session.getDocument(doc.getRef());
assertNotEquals(v1, doc.getVersionLabel());
// reset the version
session.checkIn(doc.getRef(), VersioningOption.MAJOR, "Increment major version");
doc = session.getDocument(doc.getRef());
String v3 = doc.getVersionLabel();
// when I update the doc though a event listener
doc.putContextData(DummyUpdateBeforeModificationListener.PERDORM_UPDATE_FLAG, true);
doc = session.saveDocument(doc);
// then the version change
doc = session.getDocument(doc.getRef());
assertNotEquals(v3, doc.getVersionLabel());
}
}