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 on this page
- An IDE that integrates with Maven. We provide instructions for Eclipse IDE for Java EE Developers 4.5 (Mars) or IntelliJ IDEA
- Java 17 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 - Bootstrap 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:
? Nuxeo Version: 2021.1 ? 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.
Step 3 - Link With Studio
From a command line, inside your project directory:
Link your created project to your Studio project:
$ nuxeo studio link
Fill in your credentials:
? NOS Username: ? NOS Token: [input is hidden] ? Studio Project: ? Do you want to update your Maven settings.xml file accordingly? (Y/n) Y
Encrypt Your Password
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.
Import Studio Constants
Import constants used in your Studio project:
$ nuxeo studio import
Fill in the following values:
? Constant package: com.nuxeo.studio ? Constant class name: StudioConstant
Nuxeo CLI generates a new Java class with a list of constants containing everything defined in your Studio application. By default, the Java class is created to:
contract-mgt-project/contract-mgt-project-core/src/main/java/com/nuxeo/studio/StudioConstant.java
Step 4 - Implement Your 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 page for more information about operations.
In your 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
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; import com.nuxeo.studio.StudioConstant; /** * */ @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"; @OperationMethod(collector = DocumentModelCollector.class) public DocumentModel run(DocumentModel input) throws NuxeoException { if (!(StudioConstant.CONTRACT_DOC_TYPE.equals(input.getType()))) { throw new NuxeoException("Operation works only with " + StudioConstant.CONTRACT_DOC_TYPE + " document type."); } Calendar start = (Calendar) input.getPropertyValue(StudioConstant.CONTRACT_SCHEMA_START_PROPERTY); Calendar reminder = (Calendar) start.clone(); reminder.add(Calendar.MONTH, 3); input.setPropertyValue(StudioConstant.CONTRACT_SCHEMA_REMINDER_PROPERTY, reminder.getTime()); return input; } }
Step 5 - Export Registries and Use Them in Studio
We want to make visible what is defined in our Java code into the Studio project. For instance, we'll be able to use Document.ContractUpdater
in an Automation Chain.
In your terminal, export your work:
$ nuxeo studio export
On your Studio project under Settings > Automatic Registries > Automation Operations, you should see:
Your operation can be used in Configuration > Automation > Automations Chains, as follow:
Step 6 - Unit Testing
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.
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.
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.PartialDeploy; import org.nuxeo.runtime.test.runner.TargetExtensions; @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"}) @PartialDeploy(bundle = "studio.extensions.MAVEN-ARTIFACT-ID", features = { TargetExtensions.Automation.class }) 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)); } }
Going Further
Let's learn how to package, publish, deploy and hot reload your studio project.