/* * (C) Copyright 2006-2017 Nuxeo (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: * bstefanescu */ package org.nuxeo.ecm.automation.core.test; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import javax.inject.Inject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.ecm.automation.AutomationService; import org.nuxeo.ecm.automation.OperationChain; import org.nuxeo.ecm.automation.OperationContext; import org.nuxeo.ecm.automation.core.impl.adapters.StringToProperties; import org.nuxeo.ecm.automation.core.operations.FetchContextDocument; import org.nuxeo.ecm.automation.core.operations.RestoreDocumentInput; import org.nuxeo.ecm.automation.core.operations.RunScript; import org.nuxeo.ecm.automation.core.operations.SetVar; import org.nuxeo.ecm.automation.core.operations.blob.AttachBlob; import org.nuxeo.ecm.automation.core.operations.blob.GetDocumentBlob; import org.nuxeo.ecm.automation.core.operations.document.CheckInDocument; import org.nuxeo.ecm.automation.core.operations.document.CopyDocument; import org.nuxeo.ecm.automation.core.operations.document.CreateDocument; import org.nuxeo.ecm.automation.core.operations.document.CreateVersion; import org.nuxeo.ecm.automation.core.operations.document.DeleteDocument; import org.nuxeo.ecm.automation.core.operations.document.GetDocumentChildren; import org.nuxeo.ecm.automation.core.operations.document.GetDocumentParent; import org.nuxeo.ecm.automation.core.operations.document.LockDocument; import org.nuxeo.ecm.automation.core.operations.document.MoveDocument; import org.nuxeo.ecm.automation.core.operations.document.SaveDocument; import org.nuxeo.ecm.automation.core.operations.document.SetDocumentBlob; import org.nuxeo.ecm.automation.core.operations.document.SetDocumentLifeCycle; import org.nuxeo.ecm.automation.core.operations.document.SetDocumentProperty; import org.nuxeo.ecm.automation.core.operations.document.UpdateDocument; import org.nuxeo.ecm.automation.core.operations.execution.RunDocumentChain; import org.nuxeo.ecm.automation.core.operations.execution.RunInNewTransaction; import org.nuxeo.ecm.automation.core.operations.execution.RunOperationOnList; import org.nuxeo.ecm.automation.core.operations.stack.PopDocument; import org.nuxeo.ecm.automation.core.operations.stack.PushDocument; import org.nuxeo.ecm.automation.core.scripting.Expression; import org.nuxeo.ecm.automation.core.scripting.MvelExpression; import org.nuxeo.ecm.automation.core.scripting.Scripting; import org.nuxeo.ecm.automation.core.util.Properties; 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.DocumentRef; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.test.CoreFeature; 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; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ @RunWith(FeaturesRunner.class) @Features(CoreFeature.class) @Deploy("org.nuxeo.ecm.automation.core") // For version label info @LocalDeploy("org.nuxeo.ecm.automation.core:test-operations.xml") public class CoreOperationsTest { protected DocumentModel src; protected DocumentModel dst; @Inject AutomationService service; @Inject CoreSession session; @Before public void initRepo() throws Exception { session.removeChildren(session.getRootDocument().getRef()); session.save(); src = session.createDocumentModel("/", "src", "Workspace"); src.setPropertyValue("dc:title", "Source"); src = session.createDocument(src); session.save(); src = session.getDocument(src.getRef()); dst = session.createDocumentModel("/", "dst", "Workspace"); dst.setPropertyValue("dc:title", "Destination"); dst = session.createDocument(dst); session.save(); dst = session.getDocument(dst.getRef()); } // ------ Tests comes here -------- @Test public void testScriptOperation() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(RunScript.ID).set("script", "Context[\"script_title\"] = This.title;"); service.run(ctx, chain); assertEquals(src.getTitle(), ctx.get("script_title")); } @Test public void testRunScriptOperation() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(RunScript.ID).set("script", "This.setPropertyValue(\"dc:title\",\"modified from mvel\");"); service.run(ctx, chain); String title = src.getProperty("dc:title").getValue(String.class); assertThat(title, is("modified from mvel")); } @Test public void testRunScriptWithCondition() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(RunScript.ID).set("script", "if (This.id != null && This.id != '') {This.setPropertyValue(\"dc:title\",\"modified from mvel\");}"); service.run(ctx, chain); String title = src.getProperty("dc:title").getValue(String.class); assertThat(title, is("modified from mvel")); } /** * Create | Copy | Set Property. * <p> * This is also testing {@link StringToProperties} adapter * * @throws Exception */ @Test public void testChain1() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note").set("properties", "dc:title=MyDoc"); chain.add(CopyDocument.ID).set("target", dst).set("name", "note_copy"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "mydesc"); DocumentModel out = (DocumentModel) service.run(ctx, chain); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); doc = session.getDocument(new PathRef("/src/note")); assertEquals("MyDoc", doc.getPropertyValue("dc:title")); } /** * Same as before but tests relative paths Create | Copy | Set Property. * <p> * This is also testing {@link StringToProperties} adapter * * @throws Exception */ @Test public void testChain1WithRelativePath() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); DocumentModel out = (DocumentModel) service.run(ctx, "core_chain1"); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); doc = session.getDocument(new PathRef("/src/note")); assertEquals("MyDoc", doc.getPropertyValue("dc:title")); } /** * Same as before Create | Copy | Set Property But also test properties specified using a mvel Expression * * @throws Exception */ @Test public void testMvelExpressionProperties() throws Exception { src.setPropertyValue("dc:description", "dc:title=MyDoc"); session.saveDocument(src); OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note").set("properties", new MvelExpression("This.getPropertyValue(\"dc:description\")")); chain.add(CopyDocument.ID).set("target", dst).set("name", "note_copy"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "mydesc"); DocumentModel out = (DocumentModel) service.run(ctx, chain); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); doc = session.getDocument(new PathRef("/src/note")); assertEquals("MyDoc", doc.getPropertyValue("dc:title")); } /** * Same as before but use DocumentWrapper to access properties * * @throws Exception */ @Test public void testMvelExpressionProperties2() throws Exception { src.setPropertyValue("dc:description", "dc:title=MyDoc"); session.saveDocument(src); OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note").set("properties", new MvelExpression("Document[\"dc:description\"]")); chain.add(CopyDocument.ID).set("target", dst).set("name", "note_copy"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "mydesc"); DocumentModel out = (DocumentModel) service.run(ctx, chain); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); doc = session.getDocument(new PathRef("/src/note")); assertEquals("MyDoc", doc.getPropertyValue("dc:title")); } /** * Create | Move | Set Property. */ @Test public void testChain2() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note").set("properties", new Properties("dc:title=MyDoc")); chain.add(MoveDocument.ID).set("target", dst).set("name", "note_copy"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "mydesc"); DocumentModel out = (DocumentModel) service.run(ctx, chain); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); try { doc = session.getDocument(new PathRef("/src/note")); fail("Document /src/note is not supposed to exists"); } catch (Exception e) { // test ok } } /** * Create | GetParent | Update Parent | Save | Pop | Lock. */ @Test public void testChain3() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("properties", new Properties("dc:title=MyDoc")).set("name", "note"); chain.add(PushDocument.ID); chain.add(GetDocumentParent.ID); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "parentdoc"); chain.add(SaveDocument.ID); chain.add(PopDocument.ID); chain.add(UpdateDocument.ID).set("properties", new Properties("dc:title=MyDoc2\ndc:description=mydesc")); chain.add(LockDocument.ID); chain.add(SaveDocument.ID); assertNull(src.getPropertyValue("dc:description")); DocumentModel out = (DocumentModel) service.run(ctx, chain); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc2", out.getPropertyValue("dc:title")); assertTrue(out.isLocked()); assertEquals("parentdoc", session.getDocument(src.getRef()).getPropertyValue("dc:description")); } /** * Context Fetch | Create | GetParent | Create. Context Fetch | GetChildren | Delete. */ @Test public void testChain4() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note1").set("properties", new Properties("dc:title=MyDoc1")); chain.add(GetDocumentParent.ID); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note2").set("properties", new Properties("dc:title=MyDoc2")); service.run(ctx, chain); assertEquals(2, session.getChildren(src.getRef()).size()); ctx = new OperationContext(session); ctx.setInput(src); chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(GetDocumentChildren.ID); chain.add(DeleteDocument.ID); service.run(ctx, chain); assertEquals(0, session.getChildren(src.getRef()).size()); } /** * Context Fetch | Create | GetParent | Create. Context Fetch | GetChildren | Delete. */ @Test public void testBlobChain() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); Blob blob = Blobs.createBlob("blob content"); blob.setFilename("attachment"); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "File").set("name", "file2"); chain.add(SetVar.ID).set("name", "file2").set("value", Scripting.newExpression("This")); chain.add(GetDocumentParent.ID); chain.add(CreateDocument.ID).set("type", "File").set("name", "file1"); chain.add(SetDocumentBlob.ID).set("xpath", "file:content").set("file", blob); chain.add(GetDocumentBlob.ID).set("xpath", "file:content"); chain.add(AttachBlob.ID).set("xpath", "file:content").set("document", Scripting.newExpression("file2")); service.run(ctx, chain); session.save(); blob = (Blob) session.getDocument(new PathRef("/src/file1")).getPropertyValue("file:content"); assertEquals("blob content", blob.getString()); blob = (Blob) session.getDocument(new PathRef("/src/file2")).getPropertyValue("file:content"); assertEquals("blob content", blob.getString()); } /** * Test a chain running a sub-chain. */ @Test public void testSubChain() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(RunDocumentChain.ID).set("id", "doc_subchain"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals("My Doc", doc.getTitle()); assertEquals("My Doc desc", doc.getPropertyValue("dc:description")); } /** * Alternate version - use xml properties instead of java properties when defining a properties value * * @throws Exception */ @Test public void testSubChainAlt() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(RunDocumentChain.ID).set("id", "doc_subchain_alt"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals("My Doc", doc.getTitle()); assertEquals("My Doc desc", doc.getPropertyValue("dc:description")); } /** * Test restore doc. */ @Test public void testRestore() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(SetVar.ID).set("name", "mydoc").set("value", new PathRef("/dst")); chain.add(RestoreDocumentInput.ID).set("name", "mydoc"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals(dst, doc); } /** * Test date expressions. */ @Test public void testDate() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(SetDocumentProperty.ID).set("xpath", "dc:title").set("value", Scripting.newTemplate("Now is @{CurrentDate.months(-2)}")); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertTrue(doc.getTitle().startsWith("Now is TIMESTAMP")); } /** * Create a document and copy it by giving the destination path as a template expression. */ @Test public void testStringToDocAdapters() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(SetVar.ID).set("name", "st").set("value", "st"); chain.add(SetVar.ID).set("name", "pathVar").set("value", Scripting.newTemplate("/d@{st}")); chain.add(CreateDocument.ID).set("type", "Note").set("name", "note").set("properties", "dc:title=MyDoc"); chain.add(CopyDocument.ID).set("target", Scripting.newExpression("pathVar")).set("name", "note_copy"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", "mydesc"); DocumentModel out = (DocumentModel) service.run(ctx, chain); DocumentModel doc = session.getDocument(new PathRef("/dst/note_copy")); assertEquals(out.getId(), doc.getId()); assertEquals("mydesc", out.getPropertyValue("dc:description")); assertEquals("MyDoc", out.getPropertyValue("dc:title")); doc = session.getDocument(new PathRef("/src/note")); assertEquals("MyDoc", doc.getPropertyValue("dc:title")); } @Test public void testCreateVersion() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); Expression expr = Scripting.newExpression("Document.versionLabel"); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "File").set("name", "file").set("properties", "dc:title=MyDoc"); chain.add(SetVar.ID).set("name", "versionLabel_1").set("value", expr); chain.add(CreateVersion.ID).set("increment", "Major"); chain.add(SetVar.ID).set("name", "versionLabel_2").set("value", expr); chain.add(UpdateDocument.ID).set("properties", "dc:title=MyDoc3"); chain.add(CreateVersion.ID).set("increment", "Minor"); chain.add(SetVar.ID).set("name", "versionLabel_3").set("value", expr); // update document to test if version change (auto-checkout) chain.add(SetDocumentProperty.ID).set("xpath", "dc:title").set("value", "MyDoc4"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals("0.0", ctx.get("versionLabel_1")); assertEquals("1.0", ctx.get("versionLabel_2")); assertEquals("1.1", ctx.get("versionLabel_3")); assertEquals("1.1+", doc.getVersionLabel()); assertEquals("MyDoc4", doc.getTitle()); } @Test public void testCreateVersion2() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); Expression expr = Scripting.newExpression("Document.versionLabel"); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "File").set("name", "file").set("properties", "dc:title=MyDoc"); chain.add(SetVar.ID).set("name", "versionLabel_1").set("value", expr); // update document to test if version change (it should not change) chain.add(SetDocumentProperty.ID).set("xpath", "dc:title").set("value", "MyDoc2"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals("0.0", ctx.get("versionLabel_1")); assertEquals("0.0", doc.getVersionLabel()); assertEquals("MyDoc2", doc.getTitle()); } @Test public void testCreateVersion3() throws Exception { OperationContext ctx = new OperationContext(session); ctx.setInput(src); Expression expr = Scripting.newExpression("Document.versionLabel"); OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(CreateDocument.ID).set("type", "File").set("name", "file").set("properties", "dc:title=MyDoc"); chain.add(SetVar.ID).set("name", "versionLabel_1").set("value", expr); chain.add(SetDocumentLifeCycle.ID).set("value", "approve"); chain.add(CheckInDocument.ID).set("version", "major").set("comment", "yo").set("versionVarName", "ver"); chain.add(SetVar.ID).set("name", "versionLabel_2").set("value", expr); // update document to test if version change (it should not change) chain.add(SetDocumentProperty.ID).set("xpath", "dc:title").set("value", "MyDoc2"); chain.add(SetVar.ID).set("name", "versionLabel_3").set("value", expr); chain.add(SetDocumentProperty.ID).set("xpath", "dc:title").set("value", "MyDoc3"); DocumentModel doc = (DocumentModel) service.run(ctx, chain); assertEquals("0.0", ctx.get("versionLabel_1")); assertEquals("1.0", ctx.get("versionLabel_2")); assertEquals("1.0+", ctx.get("versionLabel_3")); assertEquals("1.0+", doc.getVersionLabel()); assertEquals("MyDoc3", doc.getTitle()); DocumentRef ver = (DocumentRef) ctx.get("ver"); assertNotNull(ver); } @Test public void testRunOperatioOnList() throws Exception { try { service.putOperation(RunOnListItem.class); OperationContext ctx = new OperationContext(session); String input = "dummyInput"; ctx.setInput(input); ArrayList<String> users = new ArrayList<String>(); users.add("foo"); users.add("bar"); ctx.put("users", users); OperationChain chain = new OperationChain("testChain"); chain.add(RunOperationOnList.ID).set("list", "users").set("id", "runOnList").set("isolate", "false"); service.run(ctx, chain); String result = (String) ctx.get("result"); assertEquals("foo, bar", result); } finally { service.removeOperation(RunOnListItem.class); } } @Test public void testRunOperationOnArray() throws Exception { try { service.putOperation(RunOnListItem.class); OperationContext ctx = new OperationContext(session); String input = "dummyInput"; ctx.setInput(input); String[] groups = new String[2]; groups[0] = "tic"; groups[1] = "tac"; ctx.put("groups", groups); OperationChain chain = new OperationChain("testChain"); chain.add(RunOperationOnList.ID).set("list", "groups").set("id", "runOnList").set("isolate", "false"); service.run(ctx, chain); String result = (String) ctx.get("result"); assertEquals("tic, tac", result); } finally { service.removeOperation(RunOnListItem.class); } } @Test public void testRunInNewTxOperation() throws Exception { OperationContext ctx = new OperationContext(session); // test that the global transaction is not marked for rollback try { OperationChain chain = new OperationChain("testChain"); chain.add(RunInNewTransaction.ID) .set("id", "testExitChain") .set("isolate", "false") .set("rollbackGlobalOnError", "false"); service.run(ctx, chain); } finally { assertFalse(TransactionHelper.isTransactionMarkedRollback()); } // test that the global transaction is marked for rollback try { OperationChain chain = new OperationChain("testChain"); chain.add(RunInNewTransaction.ID) .set("id", "testExitChain") .set("isolate", "false") .set("rollbackGlobalOnError", "true"); service.run(ctx, chain); } catch (Exception e) { assertTrue(TransactionHelper.isTransactionMarkedRollback()); } // needed for session cleanup TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); } @Test // non regression test for NXP-13748 public void testSetPropertyNullValue() throws Exception { src.setPropertyValue("dc:description", "foo"); src.setPropertyValue("dc:format", "bar"); src.setPropertyValue("dc:language", "baz"); session.saveDocument(src); OperationContext ctx = new OperationContext(session); ctx.setInput(src); // check values before assertEquals("Source", src.getPropertyValue("dc:title")); assertEquals("foo", src.getPropertyValue("dc:description")); assertEquals("bar", src.getPropertyValue("dc:format")); assertEquals("baz", src.getPropertyValue("dc:language")); // run the chain OperationChain chain = new OperationChain("testChain"); chain.add(FetchContextDocument.ID); chain.add(SetDocumentProperty.ID).set("xpath", "dc:description").set("value", null); chain.add(SetDocumentProperty.ID).set("xpath", "dc:format").set("value", "expr:empty"); chain.add(SetDocumentProperty.ID).set("xpath", "dc:language").set("value", "expr:null"); DocumentModel out = (DocumentModel) service.run(ctx, chain); // check values after assertEquals("Source", out.getPropertyValue("dc:title")); assertEquals(null, out.getPropertyValue("dc:description")); assertEquals("", out.getPropertyValue("dc:format")); assertEquals(null, out.getPropertyValue("dc:language")); } }