III.F User defined tasks and commands - kghmanuel/ml-gradle GitHub Wiki

Scripting

One of the main benefits of Gradle is that you can write any Groovy code that you want in build.gradle. One use case for this is to manipulate the objects that ml-gradle adds to the Gradle Project instance. You can see what these objects are in the MarkLogicPlugin class (that line number may change over time - look for the initializeAppDeployerObjects).

Each of those objects can be manipulated within a Gradle "ext" block. The most likely object to manipulate is the instance of AppConfig. A number of properties are set on an instance of class as described in the Properties section above. You can further modify this object as shown below, as there are getters/setters for all of its properties:

ext {
  mlAppConfig {
    contentDatabaseName = "my-database" // in case you don't like the default name that ml-gradle uses
    customTokens.put("%%MY_TOKEN%%", "some-value")
  }
}

The instance of AppConfig that's stored under the property name "mlAppConfig" is very important, as it's passed to each of the Command objects that are invoked when you run "gradle mlDeploy" and "gradle mlUndeploy". By manipulating a property on AppConfig, you don't have to know what command to modify or even what command uses the property.

If needed, you can always fiddle with the set of commands that are run during mlDeploy/mlUndeploy (this is shown in sample-project as well):

ext {
  mlAppDeployer.commands.add(new MyCommand()) // MyCommand would be a class defined in the build.gradle file
  mlAppDeployer.getCommands().remove(mlAppDeployer.getCommand("DeployRestApiServersCommand")) // remove a command that you don't want to use
  mlAppDeployer.getCommand("DeployContentDatabaseCommand").setDatabaseFilename("my-content-database.xml") // change the default database filename for the content database
}

The list of commands loaded into mlAppDeployer can be found in the MarkLogicPlugin class (look for the newAppDeployer method).

Writing your own management task

ml-gradle wraps ml-app-deployer with a number of tasks, but in case you want to do something with the MarkLogic Management API that isn't yet supported by ml-gradle, you can simply write a new Gradle task and use an instance of ManageClient to easily invoke Management API endpoints. The ManageClient wraps an instance of Spring's RestTemplate and is already configured with the connection properties in your gradle.properties file.

The sample-project Gradle file shows an example of how to do this, and an example is shown below as well. This simple example invokes a database endpoint for merging a database, and it utilizes the "postJson" convenience method on ManageClient along with configuration data found in the AppConfig instance returned by "getAppConfig".

task mergeContentDatabase(type: com.marklogic.gradle.task.MarkLogicTask) {
    doLast {
        getManageClient().postJson("/manage/v2/databases/" + getAppConfig().getContentDatabaseName(), '{"operation":"merge-database"}')
    }
}

If you need to talk to the REST API port associated with your application, you can extend MarkLogicTask and get an instance of a MarkLogic DatabaseClient:

task example(type: com.marklogic.gradle.task.MarkLogicTask) {
  doLast {
    def client = newClient()
    // Do something with the client - see http://docs.marklogic.com/javadoc/client/index.html
  }
}

You can also write a task with custom XQuery code (and perhaps SJS code - it's sent over XCC, I haven't tried SJS yet) by extending XccTask:

task myTask(type: com.marklogic.gradle.task.XccTask) {
  xccUrl = "my XCC connection URL here"
  xquery = "my XQuery code here"
}

You can use Gradle variables to populate xccUrl - e.g. "${mlUsername}:${mlPassword}@${mlHost}:8000/some-database".

Writing your own command

But, in some cases, you'll want to insert new steps in the middle of the deployment process as executed by "gradle mlDeploy". This task invokes the "deploy" method on an instance of AppDeployer, which defaults to SimpleAppDeployer. SimpleAppDeployer invokes a list of Command objects in an order based on the "getExecuteSortOrder" method defined in that interface. Thus, to add a new step to the deployment process, you just need to do the following:

  • Create a new implementation of the Command interface. You can do this inline in your Gradle build file, or you can define it as a class file in your Gradle buildSrc directory. You may want to start by subclassing AbstractCommand.
  • Add an instance of your Command implementation to the SimpleAppDeployer that's created by MarkLogicPlugin.

Now, when you run "gradle mlDeploy", your command will be executed. If you need to perform some cleanup work during "gradle mlUndeploy", then just make your Command implementation implement UndoableCommand.

For an example of this, see the custom command class that's defined in the sample-project Gradle build file.

Writing a task that talks to a different port

Interacting with the Management REST API

Writing a task that talks to the Management REST API on port 8002 is straightforward - you can write a custom task that references mlManageClient, which is an instance of ManageClient:

task example {
  doLast {
    mlManageClient.putJson("...")
  }
}

Or you can extend MarkLogicTask and access the ManageClient that way as well:

task example(type: com.marklogic.gradle.task.MarkLogicTask) {
  doLast {
    getManageClient().putJson("...")
  }
}

Interacting with a Client REST API

If you need to talk to the REST API port associated with your application, you can extend MarkLogicTask and get an instance of a MarkLogic DatabaseClient:

task example(type: com.marklogic.gradle.task.MarkLogicTask) {
  doLast {
    def client = newClient()
    // Do something with the client - see http://docs.marklogic.com/javadoc/client/index.html
  }
}

As of version 3.0.0, the underlying "mlAppConfig" (an instanceof AppConfig in ml-app-deployer) object that ml-gradle creates also has some methods for creating a DatabaseClient for specific databases, e.g.

def modulesClient = mlAppConfig.newModulesDatabaseClient()
def schemasClient = mlAppConfig.newSchemasDatabaseClient()
def anyDbClient = mlAppConfig.newAppServicesDatabaseClient("any-database-name")

If you need to talk to a different port, or you'd prefer a different API for sending HTTP requests, then give HTTPBuilder a try. You'll need to include it in your buildscript dependencies:

buildscript {
  dependencies {
    classpath 'org.codehaus.groovy.modules.http-builder:http-builder:0.7'
  }
}

And then you can import the classes you need and write your task. See the HTTPBuilder Wiki for examples of how to use it.

Also see the RESTClient API in HTTPBuilder - you may find this to be a better fit than HTTPBuilder.

There is also an issue for providing nicer integration with HTTPBuilder.