The Rise

As we constantly strive to make the Nuxeo Server better, more modern and more efficient than ever, we turned our attention to the rise of the Kotlin language this time.

Nuxeo KotlinKotlin is a statically typed programming language that runs on the Java Virtual Machine and can be compiled to JavaScript source code created by JetBrains. It is designed to be an industrial-strength object-oriented language and a "better language" than Java, but still be fully interoperable with Java code, allowing organizations to make a gradual migration from Java to Kotlin.

With Google announcing that it is making it possible to write any Android app in Kotlin, the language becomes a first-class one.

Today, we are pleased to announce that you can now develop your Nuxeo bundles in Kotlin!

Kotlin and Java

Kotlin is fully interoperable with Java code and in one bundle you can compile both and make them work together!

How it Works

Project Structure

Like any Maven project structure the Kotlin bundle looks like a Nuxeo Java bundle, except this time you actually use a new folder kotlin, instead of java for your Kotlin code:

├── pom.xml
└── src
    ├── main
    │   ├── kotlin
    │   │   └── org
    │   │       └── nuxeo
    │   │           └── sample
    │   └── resources
    │       ├── META-INF
    │       └── OSGI-INF
    └── test
        └── org
            └── nuxeo
                └── kotlin
                    └── sample

Of course, you can mix both Java and Kotlin in the same bundle!

├── pom.xml
└── src
    ├── main
    │   ├── kotlin
    │   │   └── org
    │   │       └── nuxeo
    │   │           └── sample
    |   |__ java
    |   |   |__ org......
    │   └── resources

First Kotlin Bundle

In order to do that, and to make it easy for you to bootstrap your project, we already created a dedicated template using Nuxeo CLI. Let's create a new bundle with a package and a sample Operation.

# Ensure to have the latest version
nuxeo update

# You can create an empty bundle using
# nuxeo bootstrap kotlin-module
# But, following this blog, we are going to create a more complete example
mkdir kotlin-sample && cd $_
nuxeo bootstrap kotlin-operation package

You can answer as below, but feel free to get creative:

     info You'll be prompted for generation of:
     info   kotlin-sample-kotlin: kotlin-module, kotlin-operation
     info   kotlin-sample-package: package

   create Generating Multi module (Your project parent POM)
     info   Parameters: Use a parent artifact, Parent group, Parent artifact, Parent version, Import nuxeo in the `dependency management`, Nuxeo version, Project group, Project artifact, Project version, Project description
? Use a parent artifact (for instance your company's BOM or the org.nuxeo.ecm.distribution:nuxeo-distribution POM)? Yes
? Parent Group id: org.nuxeo.ecm.distribution
? Parent Artifact id: nuxeo-distribution
? Parent Version: 9.1
? Project Group id: org.nuxeo.sample
? Project Artifact id: kotlin-sample-parent
? Project Version: 1.0-SNAPSHOT
? Project Description:

   create Generate Module: kotlin-sample-kotlin

   create Generating Kotlin module
     info   Parameters: Nuxeo version, Project group, Project artifact, Project version, Project description
? Project Group id: org.nuxeo.sample
? Project Artifact id: kotlin-sample-kotlin
? Project version: 1.0-SNAPSHOT
? Project description:

   create Generating Kotlin operation
     info   Parameters: Operation class name, Operation label
? Operation class name: SampleOperation
? Operation label: My Kotlin Operation

   create Generate Module: kotlin-sample-package

   create Generating Package
     info   Parameters: Package artifact, Package version, Package name, Company name
? Package Artifact id: kotlin-sample-package
? Package Version: 1.0-SNAPSHOT
? Package name: kotlin-sample
? Company name:

First Kotlin Operation

Here is the generated Kotlin-based Operation that has roughly the same behavior as the Java one:

package org.nuxeo.sample

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 = "Document.SampleOperation", category = Constants.CAT_DOCUMENT, label="My Kotlin Operation", description="Describe here what your operation does.")
open class SampleOperation {

    @Context
    protected lateinit var session: CoreSession

    @Param(name = "path", required = false)
    protected var path: String? = null

    @OperationMethod
    fun run(): DocumentModel {
        return when {
            path.isNullOrBlank() -> session.rootDocument
            else -> session.getDocument(PathRef(path))
        }
    }
}

Of course, this operation is contributed and mentioned in the MANIFEST like any other Nuxeo bundle.

Maven Build Plugin

You will notice that we added the Kotlin Maven build plugin to compile properly as follows:

<plugin>
  <artifactId>kotlin-maven-plugin</artifactId>
  <groupId>org.jetbrains.kotlin</groupId>
  <version>${kotlin.version}</version>
  <executions>
    <execution>
      <id>compile</id>
      <goals>
        <goal>compile</goal>
      </goals>
    </execution>
    <execution>
      <id>test-compile</id>
      <goals>
        <goal>test-compile</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Deployment

Like any other Nuxeo bundle, you can build your project by running mvn package and put the bundle in your $NUXEO_HOME/nxserver/bundles folder.

But before starting the Nuxeo server, don't forget to set the related JetBrains Kotlin Runtime library in NUXEO_HOME/nxserver/lib.

The best part is that it is automatically handled using the Nuxeo Package we just created. You don't have to do anything other than deploying your package.

# Build the whole bundle
cd $PROJECT_ROOT && mvn package

# Ensure your server is stopped
$NUXEO_HOME/bin/nuxeoctl stop

# Install your bundle
$NUXEO_HOME/bin/nuxeoctl mp-install $PROJECT_ROOT/kotlin-sample-package/target/kotlin-sample-package-*.zip

# Start your server
$NUXEO_HOME/bin/nuxeoctl start

Next, let's get the Operation definition and then call it using curl:

# Get Operation Documentation
curl -u Administrator:Administrator http://localhost:8080/nuxeo/site/automation/Document.SampleOperation

# Execute the Operation
curl -H 'Content-Type:application/json+nxrequest' -X POST -d '{"params":{},"context":{}}' -u Administrator:Administrator http://localhost:8080/nuxeo/api/v1/automation/Document.SampleOperation

Awesome results!

Debugging

Add a "breakpoint" anywhere in your Operation and use your IDE remote debugging feature to connect to Nuxeo (port 8787). You will be able to execute all the steps in both Kotlin and Java!

Debug

Tests

Using Kotlin to code faster will not protect you against the need to test it. Sorry about that!

As you can see in the TestSampleOperation file, you can use our Test Framework:

@RunWith(FeaturesRunner::class)
@Features(AutomationFeature::class)
@Deploy("org.nuxeo.sample.kotlin-sample-kotlin")
open class TestSampleOperation {

    @Inject
    protected lateinit var session: CoreSession

    ...
}

Try it out and have fun with this modern and elegant language!