Getting Started

Develop Your Own Java Code

Updated: March 18, 2024

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:

  1. Create an empty folder in which to store the project:

    $ mkdir contract-mgt-project
    $ cd contract-mgt-project
    
  2. Generate a "Multi module" project structure:

    $ nuxeo bootstrap
    
  3. 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).

  4. 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.

From a command line, inside your project directory:

  1. Link your created project to your Studio project:

    $ nuxeo studio link
    
  2. 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):

  1. 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=}
    
  2. Store this password in ~/.m2/settings-security.xml like this:

    <settingsSecurity>
       <master>{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}</master>
    </settingsSecurity>
    
  3. Encrypt your Studio password:

    $ mvn --encrypt-password
    
  4. 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
  1. Import constants used in your Studio project:

    $ nuxeo studio import
    
  2. 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.

  1. In your terminal, generate an operation code template:

    $ nuxeo bootstrap operation
    
  2. You are prompted for a few details:

    ? Operation package: com.bigcorp.contractmgt
    ? Operation class name: ContractUpdater
    ? Operation label: Contract Updater
    
  3. 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));
           }
       }
    }
    
  4. 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.

  1. In your terminal, export your work:

    $ nuxeo studio export
    
  2. On your Studio project under Settings > Automatic Registries > Automation Operations, you should see:

  3. 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.

  1. 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.