III.B tasks for ml‐modules - kghmanuel/ml-gradle GitHub Wiki

In general, tasks pre-fixed with 're', e.g. mlRedeploy, mlReloadModules, mlReloadSchemas, would involve running the undeploy tasks of its subfolders (if applicable) followed by the deploy task.

Task Description
mlClearModulesDatabase Deletes potentially all of the documents in the modules database; has a property for excluding documents from deletion
mlDeleteModuleTimestampsFile Delete the properties file in the build directory that keeps track of when each module was last loaded
mlExportModules Export modules matching a URI pattern of ** (can be overridden via -PuriPattern) from the database defined by mlModulesDatabaseName (can be overridden via -PdatabaseName) to the last path defined by mlModulePaths (can be overridden via -PexportPath). For each module that cannot be exported, an error will be logged; an error will be thrown instead by setting -PlogErrors to false.
mlLoadModules Loads modules from directories defined by mlAppConfig or via a property on this task
mlReloadModules Reloads modules by first clearing the modules database and then loading modules
mlWatch Run a loop that checks for new/modified modules every second and loads any that it finds. To ignore files that are already dirty and only process new changes, include -PignoreDirty=true .

Application code under ml-modules can be loaded simply by issuing 'mlLoadModules'. This task makes use of the REST API to push resources into MarkLogic. The /v1/ext endpoint in the ML Client REST API only allows for loading one module at a time, and thus loading hundreds of modules or more can take minutes. This isn't too painful for initializing an empty modules database, but it's unacceptable during a code-test cycle.

To address this, ml-gradle keeps track of when it last loaded each module. This is done via a properties file in the "build" directory of the project (so that the properties file is not in version control). The path of the file is build/ml-last-installed-timestamp.properties. When ml-gradle tries to load a module, it first checks to see if the module's last-modified timestamp is greater than what is recorded in the properties file. If so, the module is loaded, and the properties file is updated.

Loading modules with static checking

As of version 2.5.0, you can load modules - via any task, such as mlLoadModules, and (most typically) mlWatch - with static checking of asset modules enabled. The REST API will automatically statically check a REST API service, transform, or options file, but it won't do so for asset modules - i.e. those loaded via /v1/ext and stored under /ext.

To enable this, just include the following command line property flag:

gradle mlWatch -PmlStaticCheckAssets=true

Then, when mlWatch tries to load an asset module that has a compilation error in it, the error (with line number) will be written both to stdout where mlWatch is running and to the MarkLogic ErrorLog.txt file (it's currently logged via xdmp:log; please file an issue if you'd prefer that xdmp:trace be used). This helps you know immediately when you have an issue, as opposed to finding out later when e.g. manually testing or running an automated test.

A note about library modules

This feature will attempt to static check both main and library modules. For library modules, it does some hacky parsing of the module contents to determine its namespace so that it can use xdmp:invoke on a script that imports the module. It assumes your library modules have at least this on one line (you can use either single or double quotes around the namespace):

module namespace someprefix = "any:namespace";

As long as that's the case, the feature should be able to extract the module's namespace and include it in an XQuery script that is then invoked via xdmp:invoke. If you run into problems with this, please file an issue and include your module's namespace declaration.

Also, this doesn't work for server-side JavaScript modules yet, only XQuery. If you'd like SJS support, please file an issue for that too.

Loading modules through a load balancer

ml-gradle by default loads modules via XCC and port 8000 - it's a bit faster than using the REST API (and as of 2.4.0 ml-gradle, the XccAssetLoader has a few more features than RestApiAssetLoader does).

This is sometimes a problem when loading modules through a load balancer. The load balancer will accept HTTP traffic, but an XCC request will be rejected by MarkLogic with a 400 error message asking if the server is an XDBC server.

XCC has a fix for this - the "xcc.httpcompliant" environment property must be set to "true" - see https://help.marklogic.com/knowledgebase/article/View/32/15/load-balancing-in-marklogic . You can ensure that's always the case in Gradle by doing the following:

task setXccHttpCompliant {
  System.setProperty("xcc.httpcompliant", "true")
}

mlDeploy.dependsOn setXccHttpCompliant
mlLoadModules.dependsOn setXccHttpCompliant // in case someone invokes this directly

Loading modules via SSL

ml-gradle and its underlying libraries - ml-javaclient-util and ml-app-deployer - provide support for creating an app server with a certificate template and then, in the case of a REST API server, loading modules via an SSL connection with that server.

sample-project shows an example of how this works - here are the moving parts:

  • A certificate template config file defines the template configuration.
  • The REST API server file includes a reference to the SSL certificate template (see Referring to a resource by its ID for how this ID reference works).
  • The build.gradle file adds an instance of GenerateTemporaryCertificateCommand to the mlDeploy task. This temporary certificate is then used by the REST API server (the management API offers other options for generating real certificates). Without generating this certificate, the REST API server won't be able to accept HTTP or HTTPS connections.
  • gradle.properties includes mlSimpleSsl=true. This tells ml-gradle to use a very basic SSLContext and SSLHostnameVerifier (basic in that they do no validation) when loading modules into a REST API server.

Instead of using mlSimpleSsl, you can always configure your own SSLContext and SSLHostnameVerifier. The AppConfig instance that is stored under the key "mlAppConfig" by ml-gradle has properties named "restSslContext" and "restSslHostnameVerifier" for these objects. You can set these to anything in your build.gradle file - here's a pseudocode example:

ext {
  def mySSLContext = write any Groovy code you want to create your own SSLContext
  def mySSLHostnameVerifier = write any Groovy code you want to create your own SSLHostnameVerifier
  mlAppConfig.restSslContext = mySSLContext
  mlAppConfig.restSslHostnameVerifier = mySSLHostnameVerifier
}

Note that SSLHostnameVerifier is a MarkLogic Java Client class. It includes a few built-in implementations that may suffice for you.

As of version 3.0.0, you can also manually configure the SSL components for a connection to the App-Services server:

ext {
  mlAppConfig.appServicesSslContext = mySSLContext
  mlAppConfig.appServicesSslHostnameVerifier = mlSSLHostnameVerifier
}

Debugging module loading

Under the hood, ml-gradle uses the MarkLogic Client API to connect to a MarkLogic REST API server and load modules. So when you run into errors with loading modules, it's often helpful to run a quick test that uses the Client API to confirm that you can connect to your REST API server outside the scope of ml-gradle.

Here's a task that you can customize and run for loading modules via port 8000 (this includes all "asset" modules - i.e. not REST API services, transforms, or options, which must be loaded via your application-specific REST API server):

task testLoadModule {
  doLast {
    // See https://docs.marklogic.com/javadoc/client/com/marklogic/client/DatabaseClientFactory.html
    def host = "localhost"
    def port = 8000
    def database = "example-modules"
    def username = "admin"
    def password = "admin"

    // See https://docs.marklogic.com/javadoc/client/com/marklogic/client/DatabaseClientFactory.SecurityContext.html
    def context = new com.marklogic.client.DatabaseClientFactory.DigestAuthContext(username, password)
    def client = com.marklogic.client.DatabaseClientFactory.newClient(host, port, database, context)
    try {
      client.newDocumentManager().write("/test/module.xqy", new com.marklogic.client.io.StringHandle("<hello>world</hello>"))
    } finally {
      client.release()
    }
  }
}

You can use this as a starting point for any sort of debugging test, such as for loading a service or transform. Just check out the MarkLogic Client API docs to see what calls need to be made.

Authenticating with a certificate

If port 8000 and/or your REST API server (for loading REST extensions like services, transforms, and options) requires authenticating with a certificate, you can use the below task as a starting point for debugging authenticating with that app server. It doesn't load a module; it just evaluates a simple XQuery expression, which should suffice for verifying that you can authenticate correctly.

task testAuthenticateWithCertificate {
  doLast {
    def host = "localhost"
    def port = 8123 // the port of your REST API server
    def certFile = "path/to/cert.p12"
    def certPassword = "not-required"

    // See https://docs.marklogic.com/javadoc/client/com/marklogic/client/DatabaseClientFactory.CertificateAuthContext.html
    def context = new com.marklogic.client.DatabaseClientFactory.CertificateAuthContext(certFile, certPassword)
    def client = com.marklogic.client.DatabaseClientFactory.newClient(host, port, context)
    try {
      println client.newServerEval().xquery("fn:current-dateTime()").evalAs(String.class)
    } finally {
      client.release()
    }
  }
}

Encoding issues

If you run into encoding issues with loading modules, please see https://github.com/marklogic-community/ml-gradle/issues/155 . The solution most likely involves making the following call to modify your GRADLE_OPTS:

export GRADLE_OPTS="-Dfile.encoding=utf-8"

Or set it to whatever encoding that you need for the content in your modules.

⚠️ **GitHub.com Fallback** ⚠️