/* * (C) Copyright 2014-2016 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 * Benoit Delbosc */ package org.nuxeo.elasticsearch.test; import static org.junit.Assume.assumeTrue; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.Priority; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.automation.core.util.DocumentHelper; import org.nuxeo.ecm.core.api.AbstractSession; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; 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.DocumentRef; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.VersioningOption; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 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.test.CoreFeature; import org.nuxeo.ecm.core.trash.TrashService; import org.nuxeo.ecm.core.work.api.WorkManager; import org.nuxeo.ecm.platform.tag.TagService; import org.nuxeo.elasticsearch.api.ElasticSearchAdmin; import org.nuxeo.elasticsearch.api.ElasticSearchIndexing; import org.nuxeo.elasticsearch.api.ElasticSearchService; import org.nuxeo.elasticsearch.listener.ElasticSearchInlineListener; import org.nuxeo.elasticsearch.query.NxQueryBuilder; 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.LocalDeploy; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Test "on the fly" indexing via the listener system */ @RunWith(FeaturesRunner.class) @Features({ RepositoryElasticSearchFeature.class }) @Deploy({ "org.nuxeo.ecm.platform.tag", "org.nuxeo.ecm.platform.ws", "org.nuxeo.ecm.automation.core" }) @LocalDeploy("org.nuxeo.elasticsearch.core:elasticsearch-test-contrib.xml") public class TestAutomaticIndexing { private static final String IDX_NAME = "nxutest"; private static final String TYPE_NAME = "doc"; @Inject protected CoreFeature coreFeature; @Inject protected CoreSession session; @Inject protected ElasticSearchService ess; @Inject protected TrashService trashService; @Inject ElasticSearchAdmin esa; @Inject protected ElasticSearchIndexing esi; @Inject protected TagService tagService; @Inject protected WorkManager workManager; private boolean syncMode = false; private Priority consoleThresold; private int commandProcessed; // Number of processed command since the startTransaction public void assertNumberOfCommandProcessed(int processed) throws Exception { Assert.assertEquals(processed, esa.getTotalCommandProcessed() - commandProcessed); } /** * Wait for async worker completion then wait for indexing completion */ public void waitForCompletion() throws Exception { workManager.awaitCompletion(20, TimeUnit.SECONDS); esa.prepareWaitForIndexing().get(20, TimeUnit.SECONDS); esa.refresh(); } public void activateSynchronousMode() throws Exception { ElasticSearchInlineListener.useSyncIndexing.set(true); syncMode = true; } @After public void restoreAsyncAndConsoleLog() { ElasticSearchInlineListener.useSyncIndexing.set(false); syncMode = false; restoreConsoleLog(); } protected void startTransaction() { if (syncMode) { ElasticSearchInlineListener.useSyncIndexing.set(true); } if (!TransactionHelper.isTransactionActive()) { TransactionHelper.startTransaction(); } Assert.assertEquals(0, esa.getPendingWorkerCount()); commandProcessed = esa.getTotalCommandProcessed(); } protected void hideWarningFromConsoleLog() { Logger rootLogger = Logger.getRootLogger(); ConsoleAppender consoleAppender = (ConsoleAppender) rootLogger.getAppender("CONSOLE"); consoleThresold = consoleAppender.getThreshold(); consoleAppender.setThreshold(Level.ERROR); } protected void restoreConsoleLog() { if (consoleThresold == null) { return; } Logger rootLogger = Logger.getRootLogger(); ConsoleAppender consoleAppender = (ConsoleAppender) rootLogger.getAppender("CONSOLE"); consoleAppender.setThreshold(consoleThresold); consoleThresold = null; } @Before public void setupIndex() throws Exception { esa.initIndexes(true); } @Test public void shouldNotIndexRootDocument() throws Exception { startTransaction(); // update acp on root document as it is reinit during tear down DocumentModel root = session.getRootDocument(); ACP acp = new ACPImpl(); ACL acl = new ACLImpl(); acl.add(new ACE("Administrator", "Everything", true)); acp.addACL(acl); root.setACP(acp, true); // check no indexing was performed TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(0); // check no root document from search response startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(0, searchResponse.getHits().getTotalHits()); } @Test public void shouldNotIndexRootDocumentDuringReindexAll() throws Exception { startTransaction(); // Re-index all repositories for (String repositoryName : esa.getRepositoryNames()) { esa.dropAndInitRepositoryIndex(repositoryName); esi.runReindexingWorker(repositoryName, "SELECT ecm:uuid FROM Document"); } // check no indexing was performed TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(0); // check no root document from search response startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(0, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexDocument() throws Exception { startTransaction(); // create 10 docs for (int i = 0; i < 10; i++) { DocumentModel doc = session.createDocumentModel("/", "testDoc" + i, "File"); doc.setPropertyValue("dc:title", "TestMe" + i); doc = session.createDocument(doc); } // merge 5 for (int i = 0; i < 5; i++) { DocumentModel doc = session.getDocument(new PathRef("/testDoc" + i)); doc.setPropertyValue("dc:description", "Description TestMe" + i); doc = session.saveDocument(doc); } TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(10); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(10, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexImportedDocument() throws Exception { startTransaction(); // import one doc DocumentModelImpl doc = new DocumentModelImpl("/", "testDoc", "File"); doc.setId(UUID.randomUUID().toString()); doc.setPropertyValue("dc:title", "TestMe"); session.importDocuments(Collections.singletonList(doc)); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); } @Test public void shouldNotIndexDocumentBecauseOfRollback() throws Exception { startTransaction(); // create 10 docs activateSynchronousMode(); for (int i = 0; i < 10; i++) { DocumentModel doc = session.createDocumentModel("/", "testDoc" + i, "File"); doc.setPropertyValue("dc:title", "TestMe" + i); doc = session.createDocument(doc); } // Save session to prevent NXP-14494 session.save(); TransactionHelper.setTransactionRollbackOnly(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(0); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(0, searchResponse.getHits().getTotalHits()); Assert.assertFalse(esa.isIndexingInProgress()); } @Test public void shouldUnIndexDocument() throws Exception { startTransaction(); DocumentModel doc = session.createDocumentModel("/", "testDoc", "File"); doc.setPropertyValue("dc:title", "TestMe"); doc = session.createDocument(doc); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); // now delete the document session.removeDocument(doc.getRef()); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(0, searchResponse.getHits().getTotalHits()); } @Test public void shouldReIndexDocument() throws Exception { startTransaction(); // create 10 docs for (int i = 0; i < 10; i++) { DocumentModel doc = session.createDocumentModel("/", "testDoc" + i, "File"); doc.setPropertyValue("dc:title", "TestMe" + i); doc.setPropertyValue("dc:nature", "A"); doc = session.createDocument(doc); } TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(10); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setQuery(QueryBuilders.matchQuery("dc:nature", "A")) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(10, searchResponse.getHits().getTotalHits()); int i = 0; for (SearchHit hit : searchResponse.getHits()) { i++; if (i > 8) { break; } DocumentModel doc = session.getDocument(new IdRef(hit.getId())); doc.setPropertyValue("dc:nature", "B"); session.saveDocument(doc); } TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(8); startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setQuery(QueryBuilders.matchQuery("dc:nature", "A")) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(2, searchResponse.getHits().getTotalHits()); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setQuery(QueryBuilders.matchQuery("dc:nature", "B")) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(8, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexBinaryFulltext() throws Exception { startTransaction(); activateSynchronousMode(); // this is to prevent race condition that happen NXP-16169 DocumentModel doc = session.createDocumentModel("/", "myFile", "File"); BlobHolder holder = doc.getAdapter(BlobHolder.class); holder.setBlob(new StringBlob("You know for search")); doc = session.createDocument(doc); session.save(); TransactionHelper.commitOrRollbackTransaction(); // we need to wait for the async fulltext indexing WorkManager wm = Framework.getLocalService(WorkManager.class); waitForCompletion(); startTransaction(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(1, ret.totalSize()); ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:fulltext='search'")); Assert.assertEquals(1, ret.totalSize()); } @Test public void shouldIndexLargeBinaryFulltext() throws Exception { startTransaction(); activateSynchronousMode(); // this is to prevent race condition that happen NXP-16169 DocumentModel doc = session.createDocumentModel("/", "myFile", "File"); BlobHolder holder = doc.getAdapter(BlobHolder.class); holder.setBlob(new StringBlob(new String(new char[33000]).replace('\0', 'a') + " search")); // Note that token > 32k fails only when using a disk storage elastic configuration doc = session.createDocument(doc); session.save(); TransactionHelper.commitOrRollbackTransaction(); WorkManager wm = Framework.getLocalService(WorkManager.class); waitForCompletion(); startTransaction(); DocumentModelList ret = ess.query( new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:fulltext='search'")); Assert.assertEquals(1, ret.totalSize()); } @Test public void shouldIndexLargeToken() throws Exception { assumeTrue("DB backend needs to support fields bigger than 32k", coreFeature.getStorageConfiguration().isVCSH2()); startTransaction(); DocumentModel doc = session.createDocumentModel("/", "myFile", "File"); doc.setPropertyValue("dc:source", "search foo" + new String(new char[33000]).replace('\0', 'a')); // Note that token > 32k error is raised only when using a disk storage elastic configuration doc = session.createDocument(doc); session.save(); TransactionHelper.commitOrRollbackTransaction(); WorkManager wm = Framework.getLocalService(WorkManager.class); waitForCompletion(); startTransaction(); DocumentModelList ret = ess.query( new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE dc:source LIKE 'search*'")); Assert.assertEquals(1, ret.totalSize()); } @Test public void shouldIndexOnPublishing() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel doc = session.createDocumentModel("/", "file", "File"); doc = session.createDocument(doc); // publish DocumentModel proxy = session.publishDocument(doc, folder); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(5); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); // folder, version, file and proxy Assert.assertEquals(4, searchResponse.getHits().getTotalHits()); // unpublish session.removeDocument(proxy.getRef()); DocumentModelList docs = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(4, docs.totalSize()); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(3, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexOnRePublishing() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel doc = session.createDocumentModel("/", "file", "File"); doc.setPropertyValue("dc:description", "foo"); doc = session.createDocument(doc); session.publishDocument(doc, folder); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); DocumentModelList docs = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:fulltext = 'foo' AND ecm:isVersion = 0")); Assert.assertEquals(2, docs.totalSize()); doc.setPropertyValue("dc:description", "bar"); session.saveDocument(doc); session.publishDocument(doc, folder); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); docs = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:fulltext = 'bar' AND ecm:isVersion = 0")); Assert.assertEquals(2, docs.totalSize()); } @Test public void shouldUnIndexUsingTrashService() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel doc = session.createDocumentModel("/", "file", "File"); doc = session.createDocument(doc); trashService.trashDocuments(Collections.singletonList(doc)); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(2); startTransaction(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted'")); Assert.assertEquals(1, ret.totalSize()); trashService.undeleteDocuments(Collections.singletonList(doc)); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); ret = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted'")); Assert.assertEquals(2, ret.totalSize()); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(2, searchResponse.getHits().getTotalHits()); trashService.purgeDocuments(session, Collections.singletonList(doc.getRef())); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexOnCopy() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel doc = session.createDocumentModel("/", "file", "File"); doc = session.createDocument(doc); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(2); startTransaction(); DocumentRef src = doc.getRef(); DocumentRef dst = new PathRef("/"); session.copy(src, dst, "file2"); // turn the sync flag after the action ElasticSearchInlineListener.useSyncIndexing.set(true); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .execute() .actionGet(); Assert.assertEquals(3, searchResponse.getHits().getTotalHits()); } @Test public void shouldIndexTag() throws Exception { assumeTrue("DBS does not support tags", !coreFeature.getStorageConfiguration().isDBS()); // ElasticSearchInlineListener.useSyncIndexing.set(true); startTransaction(); DocumentModel doc = session.createDocumentModel("/", "file", "File"); doc = session.createDocument(doc); tagService.tag(session, doc.getId(), "mytag", "Administrator"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); ElasticSearchInlineListener.useSyncIndexing.set(true); assertNumberOfCommandProcessed(3); // doc, tagging relation and tag startTransaction(); SearchResponse searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .setQuery(QueryBuilders.termQuery("ecm:tag", "mytag")) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); tagService.tag(session, doc.getId(), "mytagbis", "Administrator"); session.save(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(3); // doc, tagging and new tag startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .setQuery(QueryBuilders.termQuery("ecm:tag", "mytagbis")) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); tagService.untag(session, doc.getId(), "mytag", "Administrator"); session.save(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(2); // doc, tagging startTransaction(); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .setQuery(QueryBuilders.termQuery("ecm:tag", "mytagbis")) .execute() .actionGet(); Assert.assertEquals(1, searchResponse.getHits().getTotalHits()); searchResponse = esa.getClient() .prepareSearch(IDX_NAME) .setTypes(TYPE_NAME) .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) .setFrom(0) .setSize(60) .setQuery(QueryBuilders.termQuery("ecm:tag", "mytag")) .execute() .actionGet(); Assert.assertEquals(0, searchResponse.getHits().getTotalHits()); } @Test public void shouldHandleCreateDelete() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel doc = session.createDocumentModel("/folder", "note", "Note"); doc = session.createDocument(doc); TransactionHelper.commitOrRollbackTransaction(); // we don't wait for async TransactionHelper.startTransaction(); session.removeDocument(folder.getRef()); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); } @Test public void shouldHandleUpdateOnTransientDoc() throws Exception { startTransaction(); DocumentModel tmpDoc = session.createDocumentModel("/", "file", "File"); tmpDoc.setPropertyValue("dc:title", "TestMe"); DocumentModel doc = session.createDocument(tmpDoc); // Send an ES_INSERT cmd session.saveDocument(doc); // Send an ES_UPDATE merged with ES_INSERT TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); // here we manipulate the transient doc with a null docid Assert.assertNull(tmpDoc.getId()); tmpDoc.setPropertyValue("dc:title", "NewTitle"); hideWarningFromConsoleLog(); session.saveDocument(tmpDoc); restoreConsoleLog(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); DocumentModelList docs = ess.query( new NxQueryBuilder(session).nxql("SELECT * FROM Document Where dc:title='NewTitle'")); Assert.assertEquals(1, docs.totalSize()); } @Test public void shouldHandleUpdateOnTransientDocBis() throws Exception { startTransaction(); DocumentModel tmpDoc = session.createDocumentModel("/", "file", "File"); tmpDoc.setPropertyValue("dc:title", "TestMe"); DocumentModel doc = session.createDocument(tmpDoc); // Send an ES_INSERT cmd hideWarningFromConsoleLog(); session.saveDocument(doc); // Send an ES_UPDATE merged with ES_INSERT tmpDoc.setPropertyValue("dc:title", "NewTitle"); // ES_UPDATE with transient, merged session.saveDocument(tmpDoc); restoreConsoleLog(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); // commands are not factored due to the misusage of the transient docs, we don't mind assertNumberOfCommandProcessed(2); startTransaction(); DocumentModelList docs = ess.query( new NxQueryBuilder(session).nxql("SELECT * FROM Document Where dc:title='NewTitle'")); Assert.assertEquals(1, docs.totalSize()); } @Test public void shouldHandleUpdateBeforeInsertOnTransientDoc() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "section", "Folder"); session.createDocument(folder); hideWarningFromConsoleLog(); folder = session.saveDocument(folder); // generate a WARN and an UPDATE command restoreConsoleLog(); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(2); startTransaction(); } @Test public void shouldIndexOrderedFolder() throws Exception { startTransaction(); DocumentModel ofolder = session.createDocumentModel("/", "ofolder", "OrderedFolder"); ofolder = session.createDocument(ofolder); DocumentModel file1 = new DocumentModelImpl("/ofolder", "testfile1", "File"); file1 = session.createDocument(file1); DocumentModel file2 = new DocumentModelImpl("/ofolder", "testfile2", "File"); file2 = session.createDocument(file2); DocumentModel file3 = new DocumentModelImpl("/ofolder", "testfile3", "File"); file3 = session.createDocument(file3); DocumentModel folder4 = new DocumentModelImpl("/ofolder", "folder4", "Folder"); folder4 = session.createDocument(folder4); DocumentModel file = new DocumentModelImpl("/ofolder/folder4", "testfile", "File"); file = session.createDocument(file); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(6); startTransaction(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql( String.format("SELECT * FROM Document WHERE ecm:parentId='%s' ORDER BY ecm:pos", ofolder.getId()))); Assert.assertEquals(4, ret.totalSize()); Assert.assertEquals(file1.getId(), ret.get(0).getId()); Assert.assertEquals(file2.getId(), ret.get(1).getId()); Assert.assertEquals(file3.getId(), ret.get(2).getId()); session.orderBefore(ofolder.getRef(), "testfile3", "testfile2"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); // only the 4 direct children are reindexed assertNumberOfCommandProcessed(4); startTransaction(); ret = ess.query(new NxQueryBuilder(session).nxql( String.format("SELECT * FROM Document WHERE ecm:parentId='%s' ORDER BY ecm:pos", ofolder.getId()))); Assert.assertEquals(4, ret.totalSize()); Assert.assertEquals(file1.getId(), ret.get(0).getId()); Assert.assertEquals(file3.getId(), ret.get(1).getId()); Assert.assertEquals(file2.getId(), ret.get(2).getId()); } @Test public void shouldNotIndexRecursivelyVersionFolder() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); DocumentModel file1 = new DocumentModelImpl("/folder", "testfile1", "File"); file1 = session.createDocument(file1); DocumentModel file2 = new DocumentModelImpl("/folder", "testfile2", "File"); file2 = session.createDocument(file2); folder.setPropertyValue("dc:title", "v1"); folder = session.saveDocument(folder); DocumentRef v1 = folder.checkIn(VersioningOption.MAJOR, "init"); folder.setPropertyValue("dc:title", "v2"); folder = session.saveDocument(folder); DocumentRef v2 = folder.checkIn(VersioningOption.MAJOR, "update"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); // 3 docs (2 files + 1 folder checkout) + 2 versions of folder + 2 versions (because of isLastVersions) assertNumberOfCommandProcessed(7); startTransaction(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(5, ret.totalSize()); // delete the first version session.removeDocument(v1); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(1); startTransaction(); ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(4, ret.totalSize()); } @Test public void shouldIndexLatestVersions() throws Exception { createADocumentWith3Versions(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(4, ret.totalSize()); ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:isLatestVersion = 1")); Assert.assertEquals(1, ret.totalSize()); ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:isLatestMajorVersion = 1")); Assert.assertEquals(1, ret.totalSize()); Assert.assertEquals("v3", ret.get(0).getTitle()); } @Test public void shouldNotIndexLatestVersions() throws Exception { System.setProperty(AbstractSession.DISABLED_ISLATESTVERSION_PROPERTY, "true"); try { createADocumentWith3Versions(); } finally { System.clearProperty(AbstractSession.DISABLED_ISLATESTVERSION_PROPERTY); } DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(4, ret.totalSize()); // but isLatestVersion and isLatestMajorVersion are not updated ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:isLatestVersion = 1")); Assert.assertEquals(3, ret.totalSize()); ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document WHERE ecm:isLatestMajorVersion = 1")); Assert.assertEquals(3, ret.totalSize()); } protected void createADocumentWith3Versions() throws Exception { startTransaction(); DocumentModel file1 = new DocumentModelImpl("/", "testfile1", "File"); file1 = session.createDocument(file1); file1.setPropertyValue("dc:title", "v1"); file1 = session.saveDocument(file1); DocumentRef v1 = file1.checkIn(VersioningOption.MAJOR, "init v1"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); file1.setPropertyValue("dc:title", "v2"); file1 = session.saveDocument(file1); DocumentRef v2 = file1.checkIn(VersioningOption.MAJOR, "update v2"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); file1.setPropertyValue("dc:title", "v3"); file1 = session.saveDocument(file1); DocumentRef v3 = file1.checkIn(VersioningOption.MAJOR, "update v3"); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); } @Test public void shouldIndexUpdatedProxy() throws Exception { startTransaction(); DocumentModel folder1 = new DocumentModelImpl("/", "testfolder1", "Folder"); folder1 = session.createDocument(folder1); folder1 = session.saveDocument(folder1); DocumentModel file1 = new DocumentModelImpl("/", "testfile1", "File"); file1 = session.createDocument(file1); file1.setPropertyValue("dc:title", "Title before proxy update"); file1 = session.saveDocument(file1); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); // Create proxy DocumentModel proxy = session.createProxy(file1.getRef(), folder1.getRef()); proxy = session.saveDocument(proxy); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); // Now update it proxy.setPropertyValue("dc:title", "Title after proxy update"); proxy = session.saveDocument(proxy); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); startTransaction(); DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document")); Assert.assertEquals(3, ret.totalSize()); // Check that proxy was updated in ES ret = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:isProxy = 1 and dc:title='Title after proxy update'")); Assert.assertEquals(1, ret.totalSize()); Assert.assertEquals("Title after proxy update", ret.get(0).getTitle()); // Check that live document was updated in ES ret = ess.query(new NxQueryBuilder(session).nxql( "SELECT * FROM Document WHERE ecm:isProxy = 0 and dc:title='Title after proxy update'")); Assert.assertEquals(1, ret.totalSize()); Assert.assertEquals("Title after proxy update", ret.get(0).getTitle()); } @Test public void shouldIndexComplexCase() throws Exception { startTransaction(); DocumentModel folder = session.createDocumentModel("/", "folder", "Folder"); folder = session.createDocument(folder); ACP acp = new ACPImpl(); ACL acl = ACPImpl.newACL(ACL.LOCAL_ACL); acl.add(new ACE("bob", SecurityConstants.READ, true)); acp.addACL(acl); folder.setACP(acp, true); DocumentModel doc = session.createDocumentModel("/folder", "file", "File"); doc.setPropertyValue("dc:title", "File"); // upload file blob File fieldAsJsonFile = FileUtils.getResourceFileFromContext("blob.json"); try { Blob fb = Blobs.createBlob(fieldAsJsonFile, "image/jpeg"); DocumentHelper.addBlob(doc.getProperty("file:content"), fb); } catch (IOException e) { throw new NuxeoException(e); } doc = session.createDocument(doc); // session.saveDocument(doc); TransactionHelper.commitOrRollbackTransaction(); waitForCompletion(); assertNumberOfCommandProcessed(3); startTransaction(); } @Test public void sortOnUnmappedField() throws Exception { // sort on a field that does not exist on the mapping and not present in the index DocumentModelList ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document ORDER BY dc:source")); Assert.assertEquals(0, ret.totalSize()); // sort on internal field ret = ess.query(new NxQueryBuilder(session).nxql("SELECT * FROM Document ORDER BY ecm:pos")); Assert.assertEquals(0, ret.totalSize()); } }