/*
* (C) Copyright 2006-2010 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:
* Thierry Delprat
* Florent Guillaume
*/
package org.nuxeo.ecm.core.trash.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import javax.inject.Inject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.common.utils.Path;
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.IdRef;
import org.nuxeo.ecm.core.api.LifeCycleConstants;
import org.nuxeo.ecm.core.api.VersioningOption;
import org.nuxeo.ecm.core.event.EventService;
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.trash.TrashInfo;
import org.nuxeo.ecm.core.trash.TrashService;
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)
public class TestTrashService {
@Inject
protected CoreSession session;
@Inject
protected TrashService trashService;
@Inject
protected EventService eventService;
protected DocumentModel fold;
protected DocumentModel doc1;
protected DocumentModel doc2;
protected DocumentModel doc3;
protected Principal principal;
@Before
public void setUp() throws Exception {
principal = session.getPrincipal();
}
public void createDocuments() throws Exception {
fold = session.createDocumentModel("/", "fold", "Folder");
// use File as document type as Note is now automatically versioned on each update
fold = session.createDocument(fold);
doc1 = session.createDocumentModel("/fold", "doc1", "File");
doc1 = session.createDocument(doc1);
doc2 = session.createDocumentModel("/fold", "doc2", "File");
doc2 = session.createDocument(doc2);
doc3 = session.createDocumentModel("/", "doc3", "File");
doc3 = session.createDocument(doc3);
session.save();
}
protected void nextTransaction() {
TransactionHelper.commitOrRollbackTransaction();
eventService.waitForAsyncCompletion();
TransactionHelper.startTransaction();
}
@Test
public void testNameMangling() throws Exception {
createDocuments();
String mangled = trashService.mangleName(doc1);
assertTrue(mangled, mangled.startsWith("doc1._"));
assertTrue(mangled, mangled.endsWith("_.trashed"));
trashService.trashDocuments(Collections.singletonList(doc1));
doc1 = session.getDocument(doc1.getRef());
mangled = doc1.getName();
assertTrue(mangled, mangled.startsWith("doc1._"));
assertTrue(mangled, mangled.endsWith("_.trashed"));
assertEquals("doc1", trashService.unmangleName(doc1));
}
@Test
public void testBase() throws Exception {
createDocuments();
assertTrue(trashService.folderAllowsDelete(fold));
assertTrue(trashService.checkDeletePermOnParents(Arrays.asList(doc1, doc2)));
assertTrue(trashService.canDelete(Collections.singletonList(fold), principal, false));
assertTrue(trashService.canDelete(Collections.singletonList(doc1), principal, false));
assertTrue(trashService.canDelete(Collections.singletonList(doc2), principal, false));
assertFalse(trashService.canPurgeOrUndelete(Collections.singletonList(fold), principal));
assertFalse(trashService.canPurgeOrUndelete(Collections.singletonList(doc1), principal));
assertFalse(trashService.canPurgeOrUndelete(Collections.singletonList(doc2), principal));
TrashInfo info = trashService.getTrashInfo(Arrays.asList(fold, doc1, doc3), principal, false, false);
assertEquals(3, info.docs.size());
assertEquals(2, info.rootRefs.size());
assertEquals(new HashSet<Path>(Arrays.asList(new Path("/fold"), new Path("/doc3"))), info.rootPaths);
DocumentModel above = trashService.getAboveDocument(doc1,
new HashSet<Path>(Arrays.asList(new Path("/fold/doc1"))));
assertEquals(above.getPathAsString(), fold.getId(), above.getId());
}
@Test
public void testTrashPurgeUndelete() throws Exception {
createDocuments();
// file with name from collision
DocumentModel doc4 = session.createDocumentModel("/", "doc4.1400676936345", "Note");
doc4 = session.createDocument(doc4);
String doc4origname = doc4.getName();
trashService.trashDocuments(Arrays.asList(fold, doc1, doc3, doc4));
nextTransaction();
// refetch as lifecycle state is cached
fold = session.getDocument(new IdRef(fold.getId()));
doc1 = session.getDocument(new IdRef(doc1.getId()));
doc2 = session.getDocument(new IdRef(doc2.getId()));
doc3 = session.getDocument(new IdRef(doc3.getId()));
doc4 = session.getDocument(new IdRef(doc4.getId()));
assertEquals("deleted", fold.getCurrentLifeCycleState());
assertEquals("deleted", doc1.getCurrentLifeCycleState());
assertEquals("deleted", doc3.getCurrentLifeCycleState());
assertEquals("deleted", doc4.getCurrentLifeCycleState());
// doc2 done by async BulkLifeCycleChangeListener
assertEquals("deleted", doc2.getCurrentLifeCycleState());
// check names changed
assertFalse("fold".equals(fold.getName()));
assertFalse("doc1".equals(doc1.getName()));
String doc3delname = doc3.getName();
assertFalse("doc3".equals(doc3.getName()));
assertFalse(doc4origname.equals(doc4.getName()));
assertFalse("doc4".equals(doc4.getName()));
// when recursing, don't change name
assertEquals("doc2", doc2.getName());
assertTrue(trashService.canPurgeOrUndelete(Arrays.asList(fold, doc1, doc2, doc3, doc4), principal));
// purge doc1
trashService.purgeDocuments(session, Collections.singletonList(doc1.getRef()));
assertFalse(session.exists(doc1.getRef()));
// undelete doc2 and doc4
trashService.undeleteDocuments(Arrays.asList(doc2, doc4));
fold = session.getDocument(new IdRef(fold.getId()));
doc2 = session.getDocument(new IdRef(doc2.getId()));
doc4 = session.getDocument(new IdRef(doc4.getId()));
assertEquals("project", doc2.getCurrentLifeCycleState());
assertEquals("project", doc4.getCurrentLifeCycleState());
// fold also undeleted
assertEquals("project", fold.getCurrentLifeCycleState());
// check name restored
assertEquals("fold", fold.getName());
// name still unchanged
assertEquals("doc2", doc2.getName());
// doc4 was restored with a pristine name
assertEquals("doc4", doc4.getName());
// create a new file with same name as old doc3
DocumentModel doc3bis = session.createDocumentModel("/", "doc3", "Note");
doc3bis = session.createDocument(doc3bis);
assertEquals("doc3", doc3bis.getName());
// undelete doc3
trashService.undeleteDocuments(Collections.singletonList(doc3));
doc3 = session.getDocument(new IdRef(doc3.getId()));
assertEquals("project", doc3.getCurrentLifeCycleState());
// check it was renamed again during undelete
assertFalse("doc3".equals(doc3.getName()));
assertFalse(doc3delname.equals(doc3.getName()));
}
@Test
public void testUndeleteChildren() throws Exception {
createDocuments();
trashService.trashDocuments(Collections.singletonList(fold));
nextTransaction();
// refetch as lifecycle state is cached
fold = session.getDocument(new IdRef(fold.getId()));
doc1 = session.getDocument(new IdRef(doc1.getId()));
doc2 = session.getDocument(new IdRef(doc2.getId()));
assertEquals("deleted", fold.getCurrentLifeCycleState());
// doc1 & doc2 done by async BulkLifeCycleChangeListener
assertEquals("deleted", doc1.getCurrentLifeCycleState());
assertEquals("deleted", doc2.getCurrentLifeCycleState());
// undelete fold
trashService.undeleteDocuments(Collections.singletonList(fold));
nextTransaction();
fold = session.getDocument(new IdRef(fold.getId()));
doc1 = session.getDocument(new IdRef(doc1.getId()));
doc2 = session.getDocument(new IdRef(doc2.getId()));
assertEquals("project", fold.getCurrentLifeCycleState());
// children done by async BulkLifeCycleChangeListener
assertEquals("project", doc1.getCurrentLifeCycleState());
assertEquals("project", doc2.getCurrentLifeCycleState());
}
@Test
public void testTrashFolderContainingProxy() throws Exception {
createDocuments();
DocumentRef versionRef = session.checkIn(doc3.getRef(), VersioningOption.MAJOR, null);
DocumentModel version = session.getDocument(versionRef);
DocumentModel proxy = session.createProxy(versionRef, fold.getRef());
session.save();
assertEquals("project", fold.getCurrentLifeCycleState());
assertEquals("project", proxy.getCurrentLifeCycleState());
assertEquals("project", version.getCurrentLifeCycleState());
// now delete the folder
trashService.trashDocuments(Collections.singletonList(fold));
nextTransaction();
fold.refresh();
version.refresh();
assertEquals("deleted", fold.getCurrentLifeCycleState());
assertEquals("project", version.getCurrentLifeCycleState());
assertFalse(session.exists(proxy.getRef()));
}
@Test
public void testProxy() throws Exception {
createDocuments();
DocumentRef verRef = doc3.checkIn(null, null);
DocumentModel proxy = session.createProxy(verRef, fold.getRef());
session.save();
assertTrue(trashService.canDelete(Collections.singletonList(proxy), principal, false));
assertFalse(trashService.canDelete(Collections.singletonList(proxy), principal, true));
assertFalse(trashService.canPurgeOrUndelete(Collections.singletonList(proxy), principal));
}
/**
* @since 7.3
*/
@Test
public void testDeleteTwice() throws Exception {
createDocuments();
List<DocumentModel> dd = new ArrayList<DocumentModel>();
dd.add(doc1);
trashService.trashDocuments(dd);
trashService.trashDocuments(dd);
assertTrue(session.exists(doc1.getRef()));
}
@Test
public void testPlacelessDocument() throws Exception {
DocumentModel doc4 = session.createDocumentModel(null, "doc4", "Note");
doc4 = session.createDocument(doc4);
session.save();
DocumentModel above = trashService.getAboveDocument(doc4, new HashSet<Path>(Arrays.asList(new Path("/"))));
assertNull(above);
trashService.trashDocuments(Collections.singletonList(doc4));
assertFalse(session.exists(doc4.getRef()));
}
@Test
@LocalDeploy("org.nuxeo.ecm.core.test.tests:OSGI-INF/test-trash-checkin-keep.xml")
public void testTrashCheckedInDocumentKeepCheckedIn() throws Exception {
doTestTrashCheckedInDocument(true);
}
@Test
@LocalDeploy("org.nuxeo.ecm.core.test.tests:OSGI-INF/test-trash-checkin-dontkeep.xml")
public void testTrashCheckedInDocumentDontKeepCheckedIn() throws Exception {
doTestTrashCheckedInDocument(false);
}
// default platform behavior
@Test
public void testTrashCheckedInDocumentDefault() throws Exception {
doTestTrashCheckedInDocument(true);
}
protected void doTestTrashCheckedInDocument(boolean expectCheckedIn) throws Exception {
DocumentModel folder = session.createDocumentModel("/", "folder", "Folder");
folder = session.createDocument(folder);
DocumentModel doc = session.createDocumentModel("/folder", "doc", "File");
doc = session.createDocument(doc);
// check in
doc.checkIn(VersioningOption.MAJOR, null);
assertFalse(doc.isCheckedOut());
session.save();
// trash
trashService.trashDocuments(Collections.singletonList(doc));
// make sure it's still checked in (or not if compat)
doc = session.getDocument(new IdRef(doc.getId()));
assertEquals(LifeCycleConstants.DELETED_STATE, doc.getCurrentLifeCycleState());
if (expectCheckedIn) {
assertFalse(doc.isCheckedOut());
} else {
assertTrue(doc.isCheckedOut());
}
// undelete
trashService.undeleteDocuments(Collections.singletonList(doc));
// make sure it's still checked in (or not if compat)
doc = session.getDocument(new IdRef(doc.getId()));
assertEquals("project", doc.getCurrentLifeCycleState());
if (expectCheckedIn) {
assertFalse(doc.isCheckedOut());
} else {
assertTrue(doc.isCheckedOut());
}
// another checked in doc
DocumentModel doc2 = session.createDocumentModel("/folder", "doc2", "File");
doc2 = session.createDocument(doc2);
doc2.checkIn(VersioningOption.MAJOR, null);
session.save();
// following a non-delete transition does a checkout like any other modification
session.followTransition(doc2, "approve");
doc2 = session.getDocument(new IdRef(doc2.getId()));
assertEquals("approved", doc2.getCurrentLifeCycleState());
assertTrue(doc2.isCheckedOut());
}
}