/*
* (C) Copyright 2006-2011 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:
* bstefanescu
*/
package org.nuxeo.ecm.automation.core.test;
import java.util.Calendar;
import javax.inject.Inject;
import org.junit.runner.RunWith;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.nuxeo.ecm.automation.AutomationService;
import org.nuxeo.ecm.automation.OperationChain;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.OperationException;
import org.nuxeo.ecm.automation.core.operations.FetchContextDocument;
import org.nuxeo.ecm.automation.core.operations.SetVar;
import org.nuxeo.ecm.automation.core.scripting.MvelExpression;
import org.nuxeo.ecm.automation.core.scripting.MvelTemplate;
import org.nuxeo.ecm.automation.core.scripting.Scripting;
import org.nuxeo.ecm.automation.core.util.StringList;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.SimplePrincipal;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.test.CoreFeature;
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;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
@RunWith(FeaturesRunner.class)
@Features(CoreFeature.class)
@Deploy("org.nuxeo.ecm.automation.core")
@LocalDeploy("org.nuxeo.ecm.automation.core:test-operations.xml")
public class OperationChainTest {
protected DocumentModel src;
@Inject
AutomationService service;
@Inject
CoreSession session;
@Before
public void initRepo() throws Exception {
src = session.createDocumentModel("/", "src", "Folder");
src.setPropertyValue("dc:title", "Source");
src = session.createDocument(src);
session.save();
src = session.getDocument(src.getRef());
}
@After
public void clearRepo() throws Exception {
Framework.getLocalService(EventService.class).waitForAsyncCompletion();
session.removeChildren(session.getRootDocument().getRef());
}
protected void assertContextOk(OperationContext ctx, String chain, String message, String title) {
assertEquals(chain, ctx.get("chain"));
assertEquals(message, ctx.get("message"));
assertEquals(title, ctx.get("title"));
}
// ------ Tests comes here --------
/**
* <pre>
* Input: doc
* Test chain: O1 -> O2 -> O1
* O1:doc:doc | O2:doc:ref* | O1:doc:doc
* O1:ref:doc | O2:doc:doc | O1:ref:doc
* where * means the method has priority over the other with the same input.
* Expected output: (parameters in context)
* chain : "O1:doc:doc,O2:doc:ref,O1:ref:doc"
* message : "Hello 1!,Hello 2!,Hello 3!"
* title : "Source,Source,Source"
* </pre>
* <p>
* This is testing a chain having multiple choices and one choice having a higher priority.
*/
@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("o1").set("message", "Hello 1!");
chain.add("o2").set("message", "Hello 2!");
chain.add("o1").set("message", "Hello 3!");
service.run(ctx, chain);
assertContextOk(ctx, "O1:doc:doc,O2:doc:ref,O1:ref:doc", "Hello 1!,Hello 2!,Hello 3!", "Source,Source,Source");
}
/**
* Same as before but use a managed chain
*
* @throws Exception
*/
@Test
public void testManagedChain1() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
service.run(ctx, "mychain");
assertContextOk(ctx, "O1:doc:doc,O2:doc:ref,O1:ref:doc", "Hello 1!,Hello 2!,Hello 3!", "Source,Source,Source");
}
/**
* Test compiled chain
*
* @throws Exception
*/
@Test
public void testManagedChain2() throws Exception {
testManagedChain1();
}
/**
* <pre>
* Input: ref
* Test chain: O1 -> O2 -> O1
* O1:doc:doc | O2:doc:ref* | O1:doc:doc
* O1:ref:doc | O2:doc:doc | O1:ref:doc
* where * means the method has priority over the other with the same input.
* Expected output: (parameters in context)
* chain : "O1:ref:doc,O2:doc:ref,O1:ref:doc"
* message : "Hello 1!,Hello 2!,Hello 3!"
* title : "Source,Source,Source"
* </pre>
* <p>
* This test is using the same chain as in the previous test but changes the input to DocumentRef. This is also
* testing matching on derived classes (since the IdRef used is a subclass of DocumentRef)
*/
@Test
public void testChain2() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src.getRef());
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", "Hello 1!");
chain.add("o2").set("message", "Hello 2!");
chain.add("o1").set("message", "Hello 3!");
service.run(ctx, chain);
assertContextOk(ctx, "O1:ref:doc,O2:doc:ref,O1:ref:doc", "Hello 1!,Hello 2!,Hello 3!", "Source,Source,Source");
}
/**
* <pre>
* Input: doc
* Test chain: O1 -> O3 -> O3
* O1:doc:doc | O3:doc:ref | O3:doc:ref
* O1:ref:doc | O3:doc:doc | O3:doc:doc
* Expected output: (parameters in context)
* chain : "O1:doc:doc,O3:doc:doc,O3:doc:doc"
* message : "Hello 1!,Hello 2!,Hello 3!"
* title : "Source,Source,Source"
* </pre>
* <p>
* This is testing a chain having multiple choices. You can see that the second operation in chain (O3) provides 2
* ways of processing a 'doc'. But the 'O3:doc:doc' way will be selected since the other way (e.g. O3:doc:ref)
* cannot generate a complete chain path.
*/
@Test
public void testChain3() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", "Hello 1!");
chain.add("o3").set("message", "Hello 2!");
chain.add("o3").set("message", "Hello 3!");
service.run(ctx, chain);
assertContextOk(ctx, "O1:doc:doc,O3:doc:doc,O3:doc:doc", "Hello 1!,Hello 2!,Hello 3!", "Source,Source,Source");
}
/**
* Same as before but with a ctrl operation between o3 and o3
*
* @throws Exception
*/
@Test
public void testChain3WithCtrl() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", "Hello 1!");
chain.add("o3").set("message", "Hello 2!");
chain.add("ctrl").set("message", "Control!");
chain.add("o3").set("message", "Hello 3!");
service.run(ctx, chain);
assertContextOk(ctx, "O1:doc:doc,O3:doc:doc,ctrl:void:void,O3:doc:doc", "Hello 1!,Hello 2!,Control!,Hello 3!",
"Source,Source,Source,Source");
}
/**
* This is testing the parameter expressions. If you set an operation parameter that point to 'var:principal' it
* will return
*
* @throws Exception
*/
@Test
public void testExpressionParams() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
SimplePrincipal principal = new SimplePrincipal("Hello from Context!");
ctx.put("messageHolder", principal);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", new MvelExpression("Context[\"messageHolder\"].name"));
service.run(ctx, chain);
assertContextOk(ctx, "O1:doc:doc", "Hello from Context!", "Source");
}
/**
* Same as previous but test params specified as Mvel templates
*
* @throws Exception
*/
@Test
public void testTemplateParams() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
SimplePrincipal principal = new SimplePrincipal("Context");
ctx.put("messageHolder", principal);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", new MvelTemplate("Hello from @{Context[\"messageHolder\"].name}!"));
service.run(ctx, chain);
assertContextOk(ctx, "O1:doc:doc", "Hello from Context!", "Source");
}
/**
* This is testing an invalid chain. The last operation in the chain accepts as input only Principal which is never
* produced by the previous operations in the chain.
*
* @throws Exception
*/
@Test
public void testInvalidChain() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", "Hello 1!");
chain.add("o2").set("message", "Hello 2!");
chain.add("unmatched");
try {
service.run(ctx, chain);
fail("Invalid chain not detected!");
} catch (OperationException e) {
assertEquals(
"Cannot find any valid path in operation chain - no method found for operation 'o1' and for first input type 'org.nuxeo.ecm.core.api.impl.DocumentModelImpl'",
e.getMessage());
}
}
/**
* When using a null context input an exception must be thrown if the first operation doesn't accept void input.
*/
@Test
public void testInvalidInput() throws Exception {
OperationContext ctx = new OperationContext(session);
OperationChain chain = new OperationChain("testChain");
chain.add("o1").set("message", "Hello 1!");
try {
service.run(ctx, chain);
fail("Null input was handled by a non void operation!");
} catch (OperationException e) {
// test passed
}
}
/**
* <pre>
* Input: VOID
* Test chain: V1 -> V1 -> V2
* V1:void:doc | V1:void:doc | V2:void:doc
* V1:doc:doc | V1:doc:doc | V2:string:doc
* Expected output: (parameters in context)
* chain : "V1:void:doc,V1:doc:doc,V2:void:doc"
* message : "Hello 1!,Hello 2!,Hello 3!"
* title : ",/,/"
* </pre>
*
* Test void input.
*
* @throws Exception
*/
@Test
public void testVoidInput() throws Exception {
OperationContext ctx = new OperationContext(session);
OperationChain chain = new OperationChain("testChain");
chain.add("v1").set("message", "Hello 1!");
chain.add("v1").set("message", "Hello 2!");
chain.add("v2").set("message", "Hello 3!");
service.run(ctx, chain);
assertContextOk(ctx, "V1:void:doc,V1:doc:doc,V2:void:doc", "Hello 1!,Hello 2!,Hello 3!", ",/,/");
}
/**
* <pre>
* Input: VOID
* Test chain: A1 -> A1
* A1:void:docref | A1:void:docref
* A1:doc:doc | A1:doc:doc
* Expected output: (parameters in context)
* chain : "A1:void:docref,A1:doc:doc"
* message : "Hello 1!,Hello 2!"
* title : "/,/"
* </pre>
*
* Test docref to doc adapter. Test precedence of adapters over void.
*
* @throws Exception
*/
@Test
public void testTypeAdapters() throws Exception {
OperationContext ctx = new OperationContext(session);
OperationChain chain = new OperationChain("testChain");
chain.add("a1").set("message", "Hello 1!");
chain.add("a1").set("message", "Hello 2!");
service.run(ctx, chain);
assertContextOk(ctx, "A1:void:docref,A1:doc:doc", "Hello 1!,Hello 2!", "/,/");
}
/**
* <pre>
* Input: doc
* Test chain: A2
* A2:void:docref
* A2:docref:doc
* Expected output: (parameters in context)
* chain : "A2:docref:docref"
* message : "Hello 1!"
* title : "/"
* </pre>
*
* Test doc to docref adapter.
*
* @throws Exception
*/
@Test
public void testTypeAdapters2() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(session.getRootDocument());
OperationChain chain = new OperationChain("testChain");
chain.add("a2").set("message", "Hello 1!");
service.run(ctx, chain);
assertContextOk(ctx, "A2:docref:docref", "Hello 1!", "/");
}
/**
* This is testing optional parameters. Operation2 has an optional 'message' parameter. If this is not specified in
* the operation parameter map the default value will be used which is 'default message'.
*
* @throws Exception
*/
@Test
public void testOptionalParam() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o2");
service.run(ctx, chain);
assertContextOk(ctx, "O2:doc:ref", "default message", "Source");
}
/**
* This is testing required parameters. Operation1 has a required 'message' parameter. If this is not specified in
* the operation parameter map an exception must be thrown.
*
* @throws Exception
*/
@Test
public void testRequiredParam() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o1");
try {
service.run(ctx, chain);
fail("Required parameter test failure!");
} catch (OperationException e) {
// test passed
}
}
/**
* This is testing adapters when injecting parameters. Operation 4 is taking a DocumentModel parameter. We will
* inject a DocumentRef to test DocRef to DocModel adapter.
*
* @throws Exception
*/
@Test
public void testAdaptableParam() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add("o4").set("message", "Hello 1!").set("doc", src.getRef());
Object out = service.run(ctx, chain);
assertEquals(src.getId(), ((DocumentModel) out).getId());
assertContextOk(ctx, "O4:void:doc", "Hello 1!", "Source");
}
/**
* Set a context variable from the title of the input document and use it in the next operation (by returning it)
* This is also testing boolean injection.
*
* @throws Exception
*/
@Test
public void testSetVar() throws Exception {
OperationContext ctx = new OperationContext(session);
ctx.setInput(src);
OperationChain chain = new OperationChain("testChain");
chain.add(SetVar.ID).set("name", "myvar").set("value", Scripting.newExpression("Document['dc:title']"));
chain.add("GetVar").set("name", "myvar").set("flag", true);
Object out = service.run(ctx, chain);
assertEquals(src.getTitle(), out);
}
@Test
public void testStringListOperation() throws Exception {
OperationContext ctx = new OperationContext(session);
OperationChain chain = new OperationChain("testSlo");
chain.add("slo").set("emails", "a,b,c");
StringList out = (StringList) service.run(ctx, chain);
assertEquals(3, out.size());
assertTrue(out.contains("a"));
assertTrue(out.contains("b"));
assertTrue(out.contains("c"));
}
@Test
public void testCopyDates() throws Exception {
DocumentModel doc = session.createDocumentModel(src.getPathAsString(), "testDoc", "Folder");
doc.setPropertyValue("dc:title", "TestDoc");
doc = session.createDocument(doc);
session.save();
Calendar date = Calendar.getInstance();
src.setPropertyValue("dc:issued", date);
src = session.saveDocument(src);
session.save();
assertNull(doc.getPropertyValue("dc:issued"));
assertEquals(date, src.getPropertyValue("dc:issued"));
OperationContext ctx = new OperationContext(session);
ctx.setInput(doc);
// this chain copy the dc:issue from the parent to the child
DocumentModel out = (DocumentModel) service.run(ctx, "testDateCopy");
assertEquals(date, out.getPropertyValue("dc:issued"));
}
}