Here we want to add some server side Java logic that will update the contract's renegotiation date. In our example it will simply take the contract's start date and add three months to it, but you can think of integrating any custom logic in your Java class, including a connection to an external webservice or an ERP.
Prerequisites
- A Contract document type created at the previous step
- An IDE that integrates with Maven. We provide instructions for Eclipse IDE for Java EE Developers 4.5 (Mars) or IntelliJ IDEA
- Java 8 with JDK
- Maven 3.3+ (see the Installing Maven section of page Maven Usage)
- The latest stable NodeJS version should be installed on your machine
- npm 2.12.0+
Step 1 - Install Nuxeo CLI
From a command line prompt, type:
$ npm install -g nuxeo-cli
Nuxeo CLI lets you easily scaffold common Nuxeo components like empty projects, Packages, Automation Operations, Services, etc. This saves you time writing boilerplate code to focus on your code instead of the structure.
We'll use it to generate a new Nuxeo project and a custom operation.
Step 2 - Implement a Custom Operation
We want to create an operation that indicates the date of renegotiation of a contract. This will be done by fetching the document's start date and adding three months to it.
A custom operation is a Java class in which you can put custom business logic. Custom operations usually serve one of two purposes: to support business logic that is too complex to express via an Automation Chain; or to provide functionality that does not exist within existing operations.
Once created, the operation can be exposed in Nuxeo Studio and used just like any other operation via automation chains and automation scripts. See the Automation section for more information about operations.
Create Your Project
From a command line:
Create an empty folder in which to store the project:
$ mkdir contract-mgt-project $ cd contract-mgt-project
Generate a "Multi module" project structure:
$ nuxeo bootstrap
Fill in the following values, via the prompts provided by Nuxeo CLI:
? Use a parent artifact (for instance your company's BOM or the org.nuxeo.ecm.distribution:nuxeo-distribution POM)? y ? Parent Group id: org.nuxeo.ecm.distribution ? Parent Artifact id: nuxeo-distribution ? Parent Version: (leave the default option here) ? Project group id: com.bigcorp.contractmgt ? Project Artifact id: contract-mgt-project-parent ? Project version: 1.0-SNAPSHOT ? Project description: Contract management parent.
Once complete, Nuxeo CLI will automatically start the process to create a "Single Module" project (the operation will be implemented here).
Fill in the following values to generate the "Single Module" project:
? Project Group id: com.bigcorp.contractmgt ? Project Artifact id: contract-mgt-project-core ? Project version: 1.0-SNAPSHOT ? Project description: Contract management project.
Now that the project is generated, it can be used in any IDE.
Import the Project Into an IDE
Using IntelliJ IDEA
Select File / Open....
Browse to your project root folder (
contract-mgt-project
), then thepom.xml
file and click Open.Choose Open as Project.
Using Eclipse
Generate Eclipse configuration files.
$ mvn install # The following parameters (downloadSources, downloadJavadocs and useProjectReferences) are optional # For details, see Maven Eclipse Plugin documentation: https://maven.apache.org/plugins/maven-eclipse-plugin/eclipse-mojo.html $ mvn eclipse:clean eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true -Declipse.useProjectReferences=true # Linux and OS X users should run the following script to make Eclipse use different build directories than Maven: $ curl -o- https://raw.githubusercontent.com/nuxeo/nuxeo/master/fixeclipse|bash # A cross-platform script is also available for Windows users: # curl -o- https://raw.githubusercontent.com/nuxeo/nuxeo/master/scripts/fixeclipse.py|python
In Eclipses select File / Import / General / Existing Projects into Workspace.
Browse to your project root folder (
contract-mgt-project
), then click Finish.
Implement Your Operation
In a terminal, generate an operation code template:
$ nuxeo bootstrap operation
You are prompted for a few details:
? Operation package: com.bigcorp.contractmgt ? Operation class name: ContractUpdater ? Operation label: Contract Updater
Update the dependencies:
IntelliJ IDEA In IntelliJ IDEA, click on Import Changes in the Maven's popup.
Eclipse
In a terminal:
$ mvn eclipse:eclipse
In Eclipse, then right-click on the project and click Refresh (F5).
Nuxeo CLI will generate a new Java class for the operation at
contract-mgt-project/contract-mgt-project-core/src/main/java/com/bigcorp/contractmgt/ContractUpdater.java
like so:package com.bigcorp.contractmgt; import org.apache.commons.lang3.StringUtils; import org.nuxeo.ecm.automation.core.Constants; import org.nuxeo.ecm.automation.core.annotations.Context; import org.nuxeo.ecm.automation.core.annotations.Operation; import org.nuxeo.ecm.automation.core.annotations.OperationMethod; import org.nuxeo.ecm.automation.core.annotations.Param; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.PathRef; /** * */ @Operation(id=ContractUpdater.ID, category=Constants.CAT_DOCUMENT, label="Contract Updater", description="Describe here what your operation does.") public class ContractUpdater { public static final String ID = "Document.ContractUpdater"; @Context protected CoreSession session; @Param(name = "path", required = false) protected String path; @OperationMethod public DocumentModel run() { if (StringUtils.isBlank(path)) { return session.getRootDocument(); } else { return session.getDocument(new PathRef(path)); } } }
Time to fill in the skeleton and start coding! Here is the final result of
ContractUpdater.java
:package com.bigcorp.contractmgt; import java.util.Calendar; import org.nuxeo.ecm.automation.core.Constants; import org.nuxeo.ecm.automation.core.annotations.Operation; import org.nuxeo.ecm.automation.core.annotations.OperationMethod; import org.nuxeo.ecm.automation.core.collectors.DocumentModelCollector; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoException; /** * */ @Operation(id = ContractUpdater.ID, category = Constants.CAT_DOCUMENT, label = "Contract Updater", description = "On a contract, sets the reminder date to three months after its start date.") public class ContractUpdater { public static final String ID = "Document.ContractUpdater"; static final String CONTRACT_TYPE = "Contract"; static final String CONTRACT_SCHEMA = "contract"; static final String CONTRACT_START = CONTRACT_SCHEMA + ":start"; static final String CONTRACT_REMINDER = CONTRACT_SCHEMA + ":reminder"; @OperationMethod(collector = DocumentModelCollector.class) public DocumentModel run(DocumentModel input) throws NuxeoException { if (!(CONTRACT_TYPE.equals(input.getType()))) { throw new NuxeoException("Operation works only with " + CONTRACT_TYPE + " document type."); } Calendar start = (Calendar) input.getPropertyValue(CONTRACT_START); Calendar reminder = (Calendar) start.clone(); reminder.add(Calendar.MONTH, 3); input.setPropertyValue(CONTRACT_REMINDER, reminder.getTime()); return input; } }
Update the Unit Test
Nuxeo CLI automatically created a unit test class for the Operation at contract-mgt-project/contract-mgt-project-core/src/test/java/com/bigcorp/contractmgt/TestContractUpdater.java
. This test must be made to pass in order to compile and deploy your project.
Create a "dummy" component to account for necessary Studio requirements, e.g.
fakestudio-component.xml
atcontract-mgt-project/contract-mgt-project-core/src/test/resources
.Paste the following into
fakestudio-component.xml
:<component name="com.nuxeo.studio.fake"> <alias>org.nuxeo.ecm.directory.sql.storage</alias> <alias>org.nuxeo.runtime.started</alias> </component>
Replace
TestContractUpdater.java
with the following code:package com.bigcorp.contractmgt; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import java.util.Calendar; import java.util.GregorianCalendar; import javax.inject.Inject; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.ecm.automation.test.AutomationFeature; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.test.DefaultRepositoryInit; import org.nuxeo.ecm.core.test.annotations.Granularity; import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; 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; @RunWith(FeaturesRunner.class) @Features(AutomationFeature.class) @RepositoryConfig(init = DefaultRepositoryInit.class, cleanup = Granularity.METHOD) // Be sure to replace studio.extensions.MAVEN-ARTIFACT-ID // with your Studio project's symbolic name. // You can find it in Studio: // Settings / Application Information / Maven Artifact id field @Deploy({"com.bigcorp.contractmgt.contract-mgt-project-core", "studio.extensions.MAVEN-ARTIFACT-ID"}) @LocalDeploy({ "com.bigcorp.contractmgt.contract-mgt-project-core:fakestudio-component.xml" }) public class TestContractUpdater { @Inject protected CoreSession session; @Test public void shouldCallTheAutomationChain() { // Create a contract, currently stored in memory DocumentModel doc = session.createDocumentModel("/default-domain", "my-test-doc", ContractUpdater.CONTRACT_TYPE); GregorianCalendar now = new GregorianCalendar(); doc.setPropertyValue(ContractUpdater.CONTRACT_START, now); // At this stage, the reminder date should be empty assertNull(doc.getPropertyValue(ContractUpdater.CONTRACT_REMINDER)); // We'll save the document in the database which will // trigger an event handler that sets the reminder date doc = session.createDocument(doc); session.save(); // Now we'll check that the reminder date is set as expected int currentMonth = now.get(Calendar.MONTH); GregorianCalendar reminder = (GregorianCalendar) doc.getPropertyValue(ContractUpdater.CONTRACT_REMINDER); assertNotNull("Reminder date is not set, check your automation chain.", reminder); assertEquals("Reminder date is not set in three months from now", (((currentMonth + 3) > 12) ? (currentMonth - 9):(currentMonth + 3)), reminder.get(Calendar.MONTH)); } }
Replace "MAVEN-ARTIFACT-ID" in
studio.extensions.MAVEN-ARTIFACT-ID
with your Studio project's symbolic name.Note: To get the symbolic name go to Settings > Application Information in Nuxeo Studio and use the value found in the Maven Artifact id field.
If you try running the test (in Eclipse, right-click on your project and choose Run As, JUnit Test, or Run TestContractUpdater in IntelliJ IDEA), you will notice that the test fails because our Studio project is missing a few things. We need to add them to make the test pass.
Send the Operation to Studio
Build a JAR file (without running the tests); from the
contract-mgt-project
folder run:$ mvn -DskipTests package
Deploy the JAR (
contract-mgt-project/contract-mgt-project-core/target/contract-mgt-project-core-1.0-SNAPSHOT.jar
) in your Nuxeo server by copying it to $NuxeoServer/nxserver/bundles, then restart your server.Go to the local automation documentation at
<server>/nuxeo/site/automation/doc
(for examplehttp://NUXEO_SERVER/nuxeo/site/automation/doc
).In the Document category click Contract Updater, then click on the JSON definition link and copy the operation definition.
In Nuxeo Studio go to Settings > Registries > Automation Operations and paste the operation definition into the
"operations": []
array, for example:{ "operations": [ { "id" : "Document.ContractUpdater", "label" : "Contract Updater", "category" : "Document", "requires" : null, "description" : "On a contract, sets the reminder date to three months after its start date.", "url" : "Document.ContractUpdater", "signature" : [ "document", "document", "documents", "documents" ], "params" : [ ] } ] }
The operation is now available in Automation Chain editor, under the Document category.
Step 3 - Create Your Chain in Nuxeo Studio
Create an Automation Chain
In the Studio menu Automation > Automation Chains, click on New.
Call your chain
ContractUpdater
.Leave the Fetch > Context Document(s) operation and add the custom operation, available in Document > ContractUpdater.
Click on Save.
Create an Event Handler
Now create an Event Handler in order to call your operation when a contract is created.
In the Studio menu Automation > Event Handlers, click on New.
Call the event handler
SetReminderDate
.Fill in the creation wizard:
- Events: Select About to create.
- Current document has one of the types: Select your document type Contract.
- Event Handler Execution: Choose your automation chain
ContractUpdater
.
Click on Save.
Now you can try it on your server either by running the unit tests or by testing manually.
Step 4 - Test the Code
The code can either be tested through unit tests or manually. You need to bind the Studio project first to have it deployed during the unit tests or on the server when testing manually.
Bind the Studio Project
In Nuxeo Studio, under Source Control > Branch Management, release the most recent commit on your project. This will generate a version of your project that can be accessed by Maven.
It is strongly recommended that you encrypt your Studio password (aka your Nuxeo Connect account password):
Create a master password:
mvn --encrypt-master-password
The command will prompt you for your master password and produce an encrypted version, something like this:
{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}
Store this password in
~/.m2/settings-security.xml
like this:<settingsSecurity> <master>{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}</master> </settingsSecurity>
Encrypt your Studio password:
mvn --encrypt-password
Store the encrypted Studio password in your
~/.m2/settings.xml
file as below:<servers> .... <server> <id>nuxeo-studio</id> <username>your_studio_username</username> <password>your_encrypted_studio_password</password> </server> ... </servers>
This configures your Maven client to use authentication when accessing the Studio Maven repository.
Edit the
pom.xml
file incontract-mgt-project/contract-mgt-project-core
to declare the dependency on the Studio project you just made a release of:<dependencies> ... <dependency> <groupId>nuxeo-studio</groupId> <artifactId>myproject</artifactId> <version>0.0.1</version> </dependency> ... </dependencies>
The value for
artifactId
is identical to theMAVEN-ARTIFACT-ID
we referenced before. Use theversion
value from the release you created.Update the project dependencies:
IntelliJ IDEA
- IntelliJ IDEA will detect the change automatically, click on Import Changes in the Maven popup.
Eclipse
In a terminal:
$ mvn eclipse:eclipse
In Eclipse, then right-click on the project and click Refresh (F5).
Using Unit Tests
- Right-click on your unit test class and choose Run As, JUnit Test in Eclipse, or Run TestContractUpdater in IntelliJ IDEA. The tests should now pass.
Using unit tests is the recommended way to ensure a feature is working as expected. Unit tests are triggered automatically whenever you compile your project using Maven, and as such they help you in maintaining a high quality level.
Testing Manually
Start your Nuxeo server. Update your Studio package from the Update Center.
Create a new Contract with the following information:
- Title: Maintenance Contract
- Contract Owner: Nuxeo
- Starting Date: Choose today's date. You should end up with something like this:
That's it! You are ready to develop on the Nuxeo Platform.
What to do Next
You should learn to package and deploy your application in a Nuxeo Platform instance.