Loading TOC...
Java Application Developer's Guide (PDF)

Java Application Developer's Guide — Chapter 1

Introduction to the Java API

The Java Client API is an open source API for creating applications that use MarkLogic Server for document and search operations. This chapter includes the following sections:

Java Client API Overview

The Java Client API provides the following capabilities:

When working with the Java API, you first create a manager for the type of document or operation you want to perform on the database (for instance, a JSONDocumentManager to write and read JSON documents or a QueryManager to search the database). To write or read the content for a database operation, you use standard Java APIs such as InputStream, DOM, StAX, JAXB, and Transformer as well as Open Source APIs such as JDOM and Jackson.

The Java API provides a handle (a kind of adapter) as a uniform interface for content representation. As a result, you can use APIs as different as InputStream and DOM to provide content for one read() or write() method. In addition, you can extend the Java API so you can use the existing read() or write() methods with new APIs that provide useful representations for your content.

This chapter covers a number of basic architecture aspects of the Java API, including fundamental structures such as database clients, managers, and handles used in almost every program you will write with it. Before starting to code, you need to understand these structures and the concepts behind them.

The MarkLogic Java Client API is built on top of the MarkLogic REST API. The REST API, in turn, is built using XQuery that is evaluated against an HTTP App Server. For this reason, you need a REST API instance on MarkLogic Server to use the Java API. A suitable REST API instance on port 8000 is pre-configured when you install MarkLogic Server. You can also create your own on another port. For details, see Choose a REST API Instance.

Java Client API or Java XCC?

The Java API co-exists with the previously developed XCC API, as they are intended for different use cases.

You can use the Java Client API to quickly become productive in your existing Java environment, using the Java interfaces for search and document management. You can also use the Java Client API extension capability to invoke XQuery and Server-Side JavaScript code on MarkLogic Server. This enables you to take advantage of MarkLogic functionality not exposed directly through the Java Client API.

XCC provides a lower-level interface for running remote or ad hoc XQuery or Server-Side JavaScript. While XCC provides significant flexibility, it also has a somewhat steeper learning curve for developers. You can think of XCC as being to ODBC or JDBC: A low level API for sending query language directly to the server. By contrast, the Java Client API is a higher level API for working with database constructs in Java.

In terms of performance, the Java API is very similar to Java XCC for compatible queries. The Java API is a very thin wrapper over a REST API with negligible overhead.

For more information about XCC, see the XCC Developer's Guide.

Getting Started

To get started with the Java Client API, do the following:

Required Software

For information about Java platform requirements, see the following page:

https://github.com/marklogic/java-client-api

The Java Client API also requires access to a MarkLogic Server installation configured with a REST Client API instance. When you install MarkLogic 8 or later, a pre-configured REST API instance is available on port 8000. For more details, see Administering REST Client API Instances in the REST Application Developer's Guide.

For information specific to rolling upgrades, see Java Client API in the Administrator's Guide.

Make the Libraries Available to Your Application

You can make the Java Client API libraries available to your project in one of the following ways:

For more details, see the following page:

http://developer.marklogic.com/products/java

The Java Client API is an open-source project, so you can also access the sources and build your own library. For details, see Downloading the Library Source Code.

ZIP File

You can download a ZIP file from the following URL:

http://developer.marklogic.com/products/java

Download the ZIP file and uncompress it to a directory of your choice. The jar files you need to add to your class path are in the lib/ subdirectory.

Maven

To use the Maven repository, add the following to dependency to your Maven project POM file. (You may need to change the version data to match the release you're using.)

<dependency>
    <groupId>com.marklogic</groupId>
    <artifactId>marklogic-client-api</artifactId>
    <version>4.0.3</version>
</dependency>

You must also add the following to the repositories section of your pom.xml.

<repository>
    <id>jcenter</id>
    <url>http://jcenter.bintray.com</url>
</repository>
Gradle

If you use Gradle as your build tool, you must use Gradle version 1.7 or later. Add the following to your build.gradle file. Modify the version number as needed.

compile group: 'com.marklogic', 
name: 'marklogic-client-api', 
version: '4.0.3'

Add the following to your build.gradle repositories section:

jcenter()

Choose a REST API Instance

The Java API implementation interacts with MarkLogic Server using the MarkLogic REST Client API. Therefore you must have access to a REST API instance in MarkLogic Server before you can run an application that uses the Java Client API.

A REST API instance includes a specially configured HTTP App Server capable of handling REST Client API requests, a content database, and a modules database. MarkLogic Server comes with a suitable REST API instance attached to the Documents database, listening on port 8000.

The examples in this guide assume you're using the pre-configured REST API instance on port 8000 of localhost. If you want to create and use a different REST instance, see , see Administering REST Client API Instances in the REST Application Developer's Guide.

Each application must use a separate modules database and REST API instance.

Create Users

You might need to create MarkLogic Server users with appropriate security roles, or give additional privileges to existing users.

Any user who reads data will need at least the rest-reader role and any user that writes data will need at least the rest-writer role.

REST instance configuration operations, such as setting instance properties require the rest-admin role. For details, see REST Server Configuration.

Some operations require additional privileges. For example, a DatabaseClient that connects to a database other than the default database associated with the REST instance must have the http://marklogic.com/xdmp/privileges/xdmp-eval-in privilege. Using the ServerEvaluationCall interface also requires special privileges; for details, see Evaluating an Ad-Hoc Query or Server-Side Module.

Note that MarkLogic Server Administration is not exposed in Java, so operations such as creating indices, creating users, creating databases, etc. must be done via the Admin Interface, REST Management API, or other MarkLogic Server administration tool. The server configuration component of the Java API is restricted to configuration operations on the REST instance.

For details, see Security Requirements in the REST Application Developer's Guide.

Explore the Examples

The Java Client API distribution includes several examples in the examples/ directory. The examples include the following packages:

  • com.marklogic.client.example.cookbook: A collection of small examples of using the core features of the API, such as document operations and search. Most of the example code in this guide is drawn from the Cookbook examples.
  • com.marklogic.client.example.handle: Examples of using handles based on open source document models, such as JDOM or Jackson. Examples of handle extensions that read or write database documents in a new way.
  • com.marklogic.client.example.extension: A collection of extension classes and examples for manipulating documents in batches.

For instructions on building and running the examples, see the project wiki on GitHub:

http://github.com/marklogic/java-client-api/wiki/Running-the-Examples

Creating, Working With, And Releasing a Database Client

Your application must create at least one DatabaseClient object before it can interact with MarkLogic using the Java Client API. The following topics cover key things you should know about the DatabaseClient interface.

The Role of a Database Client

A DatabaseClient object encapsulates the information needed to connect to MarkLogic, such as the host and port of a REST API instance, the database to operate on, and the authentication context. Internally, each DatabaseClient object is associated with a connection pool, as described in Connection Management and Configuration.

Most tasks you perform using the Java Client API are handled by a manager object. For example, you use a QueryManager to search the database and a DocumentManager to read, update, and delete documents. You create manager objects using factory methods on DatabaseClient, such as newQueryManager and newDocumentManager.

Expected Database Client Lifetime

Best practice is to maintain a single, shared reference to a DatabaseClient object for the lifetime of your application's interaction MarkLogic, rather than frequently creating and destroying client objects.

You need multiple DatabaseClient objects if you need to connect to multiple databases or to connect to MarkLogic as multiple users. You must create a different DatabaseClient instance for each combination of (host, port, database, authentication context). Again, it is best to keep these instances around throughout their potential useful lifetime, rather than repeatedly recreating them.

You can one DatabaseClient object across multiple threads. After initial configuration, a DatabaseClient object is thread safe.

Connection Management and Configuration

Internally, the Java Client API maintains an OkHttpClient connection pool that is shared by all DatabaseClient objects. The connection pool efficiently re-uses connections whether you use a single DatabaseClient instance throughout the lifetime of your application or create and discard DatabaseClient objects on demand.

Whenever a DatabaseClient object makes a request to MarkLogic, an available connection is drawn from the connection pool. New connections are created on demand, as needed.

A DatabaseClient object returns its connection to the pool once it receives and processes the HTTP request on whose behalf it claimed the connection. A connection in the pool persists until it is explicitly released or times out due to idleness. The default maximum idle time is 5 minutes.

No state information is maintained with a connection. All cookies are discarded unless a multi-statement (multi-request) transaction is in use. The cookies associated with a multi-statement transaction are cached on the transaction object rather than with the connection.

You can adjust the connection pool configuration by implementing OkHttpClientConfigurator and calling its configure method. However, such adjustments depend on Java Client API internals and will be ignored if a future version of the API uses a different HTTP client implementation.

Creating a Database Client

To create a database client, use the com.marklogic.client.DatabaseClientFactory.newClient() method. For example, the following client connects to the default content database associated with the REST instance on port 8000 of localhost using digest authentication.

DatabaseClient client = 
  DatabaseClientFactory.newClient(
    "localhost", 8000,
    new DatabaseClientFactory.DigestAuthContext("myuser", "mypassword"));

You can also create clients that connect to a specific content database. For example, the following client also connects to the REST instance on port 8000 of localhost, but all operations are performed against the database MyDatabase:

DatabaseClient client = 
  DatabaseClientFactory.newClient(
    "localhost", 8000, "MyDatabase",
    new DatabaseClientFactory.DigestAuthContext("myuser", "mypassword"));

To use a database other than the default database associated with the REST instance requires a user with the following privilege or the equivalent role: http://marklogic.com/xdmp/privileges/xdmp-eval-in.

The host and port values must be those of a REST API instance. When you install MarkLogic, a REST API instance associated with the Documents database is pre-configured for port 8000. You can also create your own instance.

The authentication context object should match the configuration of the REST API instance. For more details, see Authentication and Connection Security.

Connecting Through a Load Balancer

When your application connects to MarkLogic through a load balancer, you should follow these guidelines:

  • Configure your DatabaseClient objects to make a GATEWAY type connection. This tells the Java Client API that direct connections to hosts in your MarkLogic cluster are not available.
  • Configure your load balancer and MarkLogic cluster timeouts to be consistent with each other. Unavailable hosts should be invalidated by the load balancer only after the MarkLogic host timeout. Cookies should expire only after the MarkLogic session timeout.

For most Java Client API operations, the connection type is transparent. However, features such as the Data Movement SDK need to know whether or not all traffic must go through a gateway host.

The default connection type for a DatabaseClient is DIRECT, meaning that the Java Client API can make direct connections to hosts in your MarkLogic cluster if necessary.

To configure a DatabaseClient for a gateway connection, pass a DatabaseClient.ConnectionType value of GATEWAY as the last parameter to DatabaseClientFactory.newClient. For example:

DatabaseClient client = 
  DatabaseClientFactory.newClient(
    "localhost", 8000, "MyDatabase",
    new DatabaseClientFactory.DigestAuthContext("myuser", "mypassword"),
    DatabaseClient.ConnectionType.GATEWAY);

For additional, context-specific load balancer guidelines, see the following topics:

Releasing a Database Client

When you no longer need a client and want to release connection resources, use the DatabaseClient object's release() method.

client.release();

DatabaseClient objects efficiently manage connection resources and are expected to be long lived. You do not need to release and re-create client objects just because your application might not require a connection for an extended time. For more details, see Expected Database Client Lifetime and Connection Management and Configuration.

Authentication and Connection Security

This section provides an overview of several methods for securing the communication between your client application and MarkLogic. See the following topics for details:

Creating a SecurityContext Object

One of the inputs to DatabaseClientFactory.newClient is a SecurityContext object. This object tells the API what credentials to use to authenticate with MarkLogic. You can select from authentication methods such as Kerberos, digest, and basic.

For example, the database client created by the following statement uses digest authentication. The username and password are those of a user configured into MarkLogic.

import com.marklogic.client.DatabaseClientFactor.DigestAuthContext;
...
DatabaseClient client = DatabaseClientFactory.newClient(
    "localhost", 8000, new DigestAuthContext(username, password));

The authentication context object should match the configuration of the REST API instance. Kerberos based authentication is most secure. Basic authentication sends the password in obfuscated, but not encrypted, mode. Digest authentication encrypts passwords sent over the network.

You can connect to MarkLogic using SSL by attaching SSL configuration information to the security context. For details, see Connecting to MarkLogic with SSL.

For more information about user authentication, see Authenticating Users in the Security Guide.

Using Kerberos Authentication

Use the following steps to configure your MarkLogic installation and client application environment for Kerberos authentication:

Your client host must be running Linux in order to use Kerberos with the Java Client API.

Configuring MarkLogic to Use Kerberos

Before you can use Kerberos authentication, you must configure MarkLogic to use external security. If your installation is not already configured for Kerberos, you must perform at least the following steps:

  1. Create a Kerberos external security configuration object. For details, see Creating an External Authentication Configuration Object in the Security Guide.
  2. Create a Kerberos keytab file and install it in your MarkLogic installation. For details, see Creating a Kerberos keytab File in the Security Guide.
  3. Create one or more users associated with an external name. For details, see Assigning an External Name to a User in the Security Guide.
  4. Configure your App Server to use kerberos-ticket authentication. For details, see Configuring an App Server for External Authentication in the Security Guide.

For more details, see External Security in the Security Guide.

Configuring Your Client Host for Kerberos

On the client, the Java Client API must be able to access a Ticket-Granting Ticket (TGT) from the Kerberos Key Distribution Center. The API uses the TGT to obtain a Kerberos service ticket.

Follow these steps to make a TGT available to the client application:

  1. Install MIT Kerberos in your client environment if it is not already installed. You can download MIT Kerberos from http://www.kerberos.org/software/index.html.
  2. If this is a new installation of MIT Kerberos, configure your installation by editing the krb5.conf file. For details, see https://web.mit.edu/kerberos/krb5-1.15/doc/admin/conf_files/krb5_conf.html.

    On Linux, Java expects this file to be located in /etc/ by default. Java uses the conf file to determine your default realm and the KDC for that realm.

    If your krb5.conf file contains a setting for default_ccache_name, the value must be a file reference of the form FILE:/tmp/krb5cc_%{uid}. This is required because the Java Client API sets the useTicketCache option of Krb5LoginModule to true. For more details, see the javadoc for com.sun.security.auth.module.Krb5LoginModule.

  3. Use kinit or a similar tool on your client host to create and cache a TGT with the Kerberos Key Distribution Center. The principal supplied to kinit must be one you associated with a MarkLogic user when performing the steps in Configuring MarkLogic to Use Kerberos.

For more details, see the following topics:

Creating a Database Client that Uses Kerberos

In your client application, use KerberosAuthContext for your security context object. For example:

import com.marklogic.client.DatabaseClientFactory.KerberosAuthContext;
...
DatabaseClient client = DatabaseClientFactory.newClient(
    "localhost", 8000, new KerberosAuthContext());

You do not need to pass an explicit externalName parameter to KerberosAuthContext unless you have multiple principals authenticated in your ticket cache and need to specify which one to use.

For a working example, see the project on GitHub:

The working example includes comments that provide suggestions for setting up a Kerberos configuration in a production environment.

https://github.com/marklogic/java-client-api/blob/master/marklogic-client-api/src/main/java/com/marklogic/client/example/cookbook/KerberosSSLClientCreator.java

Connecting to MarkLogic with SSL

You can use the security context to specify whether or not to use a secure SSL connection to communicate with MarkLogic. The App Server you connect to must also be configured to accept SSL connections. By default, the Java Client API does not use SSL.

For example, the database client created by the following statement uses digest authentication and an SSL connection:

// create a trust manager
// (note: a real application should verify certificates. This
// naive trust manager which accepts all the certificates should be replaced
// by a valid trust manager or get a system default trust manager
// which would validate whether the remote authentication credentials
// should be trusted or not.)
TrustManager naiveTrustMgr[] = new X509TrustManager[] {
    new X509TrustManager() {
      @Override
      public void checkClientTrusted(X509Certificate[] chain, String authType)       {
      }

      @Override
      public void checkServerTrusted(X509Certificate[] chain, String authType)       {
}

      @Override
      public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
      }
  }
};

// create an SSL context
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
/*
 * Here, we use a naive TrustManager which would accept any certificate
 * which the server produces. But in a real application, there should be a
 * TrustManager which is initialized with a Keystore which would determine
 * whether the remote authentication credentials should be trusted or not.
 *
 * If we init the sslContext with null TrustManager, it would use the
 * <java-home>/lib/security/cacerts file for trusted root certificates, if
 * javax.net.ssl.trustStore system property is not set and
 * <java-home>/lib/security/jssecacerts is not present. See this link for
 * more information on TrustManagers -
 * http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/
 * JSSERefGuide.html
 *
 * If self signed certificates, signed by CAs created internally are used,
 * then the internal CA's root certificate should be added to the keystore.
 * See this link -
 * https://docs.oracle.com/cd/E19226-01/821-0027/geygn/index.html for adding
 * a root certificate in the keystore.
 */
sslContext.init(null, naiveTrustMgr, null);

// create the client
// (note: a real application should use a COMMON, STRICT, or implemented hostname verifier)

DatabaseClient client = DatabaseClientFactory.newClient(
  props.host, props.port,
  new DigestAuthContext(props.writerUser, props.writerPassword)
    .withSSLContext(sslContext, (X509TrustManager) naiveTrustMgr[0])
    .withSSLHostnameVerifier(SSLHostnameVerifier.ANY));

The SSLContext object represents a secure socket protocol implementation which acts as a factory for secure socket factories. For more information about creating and working with SSLContext objects, see Accessing SSL-Enabled XDBC App Servers in the XCC Developer's Guide.

For even more security, you can also include a DatabaseClientFactory.SSLHostnameVerifier object to check if a hostname is acceptable.

For a working example, see the project on GitHub:

The working example includes comments that provide suggestions for configuring SSL in a production environment.

https://github.com/marklogic/java-client-api/blob/master/marklogic-client-api/src/main/java/com/marklogic/client/example/cookbook/SSLClientCreator.java

For more information about secure communication with MarkLogic, see the Security Guide.

Using SAML Authentication

Your client application is responsible for acquiring a SAML assertions token from the SAML Identity Provider (IDP). You can then use the SAML assertions token to make requests to the MarkLogic App Server with the MarkLogic Client Java API. That division of responsibility makes it possible for your application to adapt to a wide variety of possible SAML scenarios and IDPs.

After configuring the MarkLogic App Server to authenticate with the SAML IDP, specify a SAMLAuthContext as the SecurityContext when calling DatabaseClientFactory to create a new DatabaseClient.

You can construct a SAMLAuthContext in any of three ways, depending on your approach to authorization:

  • If you plan to finish using the DatabaseClient before the SAML assertions token expires, you can call the SAMLAuthContext constructor with the SAML assertions token.
  • If you need to extend the expiration of the SAML assertions token before you finish using the DatabaseClient, you can call the SAMLAuthContext constructor with an ExpiringSAMLAuth object and a callback that renews the SAML assertions token with the SAML IDP.

    The ExpiringSAMLAuth object provides getters for the SAML assertions token and the expiration timestamp. Your client application can construct an ExpiringSAMLAuth object by calling the SAMLAuthContext.newExpiringSAMLAuth factory method.

    The renewer callback conforms to the SAMLAuthContext.RenewerCallback functional interface by taking the initial ExpiringSAMLAuth object as input and returning an Instant with the new expiration timestamp for the renewed SAML assertions token, as in the following example:

    class MyClass {
       Instant renewer(ExpiringSAMLAuth authorization) {
         .... call to IDP ....
       }
    }
  • If you need to get a new SAML assertions token before you finish using the DatabaseClient, you can call the SAMLAuthContext constructor with a callback that authorizes with the SAML IDP by getting a new SAML assertions token.

    The authorizer callback conforms to the SAMLAuthContext.AuthorizerCallback functional interface that takes an ExpiringSAMLAuth object as input and returns an ExpiringSAMLAuth object with the new SAML assertions token and an expiration timestamp, as shown in the following example:

    class MyClass {
       ExpiringSAMLAuth authorizer(ExpiringSAMLAuth previous) {
         .... call to IDP ....
       }
    }

    On the first call, the ExpiringSAMLAuth parameter is null because no existing authorization exists. Your callback can construct an ExpiringSAMLAuth object by calling the SAMLAuthContext.newExpiringSAMLAuth factory method.

Tradeoffs to consider when choosing whether to renew or reauthorize include the following:

  • The renewer callback executes in a background thread, allowing continued requests to the MarkLogic appserver while renewing the SAML assertions token, improving utilization and performance. Extending the life of the SAML assertions token, however, could increase vulnerability in less secure environments.
  • The authorizer callback blocks requests to the MarkLogic appserver while getting a new SAML assertions token, reducing utilization and performance but maintaining the highest level of security.

You can reduce the expiration time to allow for network latency and the IDP response generation. Renewer and authorizer callbacks are called in advance of the stated expiration time to reduce the possibility that the SAML assertions token expires as a request is sent to the MarkLogic appserver.

If you need to maintain state between calls to a renewer or authorizer callback, you can implement the ExpiringSAMLAuth interface with your own class instead of calling the SAMLAuthContext.newExpiringSAMLAuth factory method to construct a default instance.

Apart from the specifics of acquiring the SAML assertions token, the use of a DatabaseClient remains the same:

  • Multiple threads can use the same DatabaseClient object.
  • DatabaseClient objects can be created with different authorizations (including different SAML assertions tokens).

A Basic Hello World Method

The following code is a basic method that creates a new document in the database. Digest authentication is used in this example; for more details, see Authentication and Connection Security.

public static void run(String host, int port, String user, String
                       password, Authentication authType) {

// Create the database client
DatabaseClient client = DatabaseClientFactory.newClient(
    host, port, new DigestAuthContext(username, password));

// Make a document manager to work with text files.
TextDocumentManager docMgr = client.newTextDocumentManager();

// Define a URI value for a document.
String docId = "/example/text.txt";

// Create a handle to hold string content.
StringHandle handle = new StringHandle();

// Give the handle some content
handle.set("A simple text document");

// Write the document to the database with URI from docId
// and content from handle
docMgr.write(docId, handle);

// release the client
client.release();
}

The above code is a slightly modified version of the run method from the com.marklogic.client.example.cookbook.ClientCreator cookbook example. It, along with a number of other basic example applications for the Java API, is located in example/com/marklogic/client/example/cookbook directory found in the zip file containing the Java API.

Document Managers

Different document formats are handled by different document manager objects, which serve as an interface between documents and the database connection. The package com.marklogic.client.document includes document managers for binary, XML, JSON, and text. If you don't know the document format, or need to work with documents of multiple formats, use a generic document manager. DatabaseClient instances have factory methods to create a new com.marklogic.client.document.DocumentManager of any subtype.

BinaryDocumentManager binDocMgr = client.newBinaryDocumentManager();
XMLDocumentManager XMLdocMgr = client.newXMLDocumentManager();
JSONDocumentManager JSONDocMgr = client.newJSONDocumentManager();
TextDocumentManager TextDocMgr = client.newTextDocumentManager();
GenericDocumentManager genericDocMgr = client.newGenericDocumentManager();

Your application only needs to create one document manager for any given type of document, no matter how many of that type of document it works with. So, even if you expect to work with, say, 1,000,000,000 JSON documents, you only need to create one JSONDocumentManager object.

Document managers are thread safe once initially configured; no matter how many threads you have, you only need one document manager per document type.

If you make a mistake and try to use the wrong type of document with a document manager, the result depends on the combination of types. For example, a BinaryDocumentManager will try to interpret the document content as binary. JSONDocumentManager and XMLDocumentManager are the most particular, since if a document is not in their format, it will not parse. Most of the time, you will get an exception error, with FailedRequestException the default if the manager cannot determine the document type.

Streaming

To stream, you supply an InputStream or Reader for the data source, not only when reading from the database but also when writing to the database. This approach allows for efficient write operations that do not buffer the data in memory. You can also use an OutputWriter to generate data as the API is writing the data to the database.

When reading from the database using a stream, be sure to close the stream explicitly if you do not read all of the data. Otherwise, the resources that support reading continue to exist.

Using Handles for Input and Output

The Java Client API uses Handles to for I/O when interacting with MarkLogic. See the following topics for more details:

Handle Overview

Content handles are key to working with the Java Client API. Handles make use of the Adapter design pattern to enable strongly typed reading and writing of a diverse and extensible set of content formats. For example, you can create a com.marklogic.client.io.DOMHandle to read or write XML DOM data.

// reading
XMLDocumentManager docMgr = client.newXMLDocumentManager();
Document doc = docMgr.read(docURI, new DOMHandle()).get();

// writing
docMgr.write(docURI, new DOMHandle(someDocument));

You can also create a com.marklogic.client.io.JacksonHandle to read or write JSON data.

// reading
JSONDocumentManager JSONDocMgr = client.newJSONDocumentManager();
JsonNode node = JSONDocMgr.read(docURI, new JacksonHandle()).get();

// writing
JSONDocMgr.write(docURI, new JacksonHandle(someJsonNode));

The Java Client API pre-defines many handle implementations. The following packages contain handle classes:

  • com.marklogic.client.io - Handles classes for standard representations such as String, File, and DOM.
  • com.marklogic.extra - Handle classes for 3rd party formats such as DOM4J and GSON. Using these handle classes requires 3rd party libraries that are not included in the Java Client API distribution.

Some handles support both read and write operations. For example, you can use a FileHandle for reading and writing files. Some handles have a special purpose. For example, you use SearchHandle for processing the results of a search operation. For a complete list of handles and what they do, see the com.marklogic.client.io package in the Java Client API Documentation.

Handles are not thread safe. Whenever you create a new thread, you will have to also create new handle objects to use while in that thread.

Some Java Client API methods enable you to use I/O short cuts that do not require explicit creation of a handle. These shortcut methods always have an As suffix, such as readAs. For example, the XMLDocumentManager.read method shown above has an XMLDocumentManager.readAs counterpart that implicitly creates the handle for you. For example:

// reading
Document doc = docMgr.readAs(docURI, Document.class);

// writing
docMgr.writeAs(docURI, someDocument);

Likewise, the JSONDocumentManger.read method shown above has an JSONDocumentManager.readAs counterpart that implicitly creates the handle for you.

// reading
JsonNode node = JSONDocMgr.readAs(docURI, JsonNode.class);

// writing
JSONDocMgr.writeAs(docURI, someJsonNode);

These shortcut methods are not more efficient, but they can improve the readability of your code. For more details, see Shortcut Methods as an Alternative to Creating Handles.

Specifying Content Format

Some handles can be used with multiple document formats. For example, an InputStream can provide content in any format, so InputStreamHandle can be used for any document format. Where content format is not explicit in the handle type, use the handle's setFormat method to specify it. For example, the following call tells the Java Client API that the handle can be used with JSON content:

new InputStreamHandle().setFormat(Format.JSON);

You cannot set a format for all handle types. For example, a DOMHandle can only be used for reading and writing XML, so you cannot specify a format.

Handle Type Quick Reference

Not all handles support all content types. In addition, though most handles can be used for either reading or writing, some are more limited. This section provides a quick guide to the content formats, operations, and data types supported by each handle class. Special purpose handle classes, such as SearchHandle, are not included.

Handle Class Content Format Supported Java Type
XML Text JSON Binary
BytesHandle
RW RW RW RW byte[]
DocumentMetadataHandle
RW MarkLogic proprietary XML format; for details, see XML Metadata Format in the REST Application Developer's Guide.
DOMHandle
RW org.w3c.dom.Document
FileHandle
RW RW RW RW java.io.File
InputSourceHandle
RW org.xml.sax.InputSource
InputStreamHandle
RW RW RW RW java.io.InputStream
JacksonHandle
RW com.fasterxml.jackson.databind.JsonNode
JacksonDatabindHandle
RW your POJO class
JacksonParserHandle
RW com.fasterxml.jackson.core.JsonParser
JAXBHandle
RW your POJO class
OutputStreamHandle
W W W W java.io.OutputStream
ReaderHandle
RW RW RW java.io.Reader
SourceHandle
RW javax.xml.transform.Source
StringHandle
RW RW RW String
XMLEventReaderHandle
RW javax.xml.stream.XMLEventReader
XMLStreamReaderHandle
RW javax.xml.stream.XMLStreamReader

Handle Example

The following code uses a DOMHandle to read an XML document from the server into an in-memory DOM object:

XMLDocumentManager docMgr = client.newXMLDocumentManager();
DOMHandle handle = new DOMHandle();
docMgr.read(docURI, handle);
org.w3c.dom.Document document = handle.get();

The following code uses a JacksonHandle to read a JSON document from the server into an in-memory JsonNode:

JSONDocumentManager JSONDocMgr = client.newJSONDocumentManager();
JacksonHandle handleJson = new JacksonHandle();
JSONDocMgr.read(docURI, handleJson);
com.fasterxml.jackson.databind.JsonNode node = handleJson.get();

The following code uses a DOMHandle to write an XML document to MarkLogic. Assume document is some previously initialized in-memory XML DOM document.

XMLDocumentManager docMgr = client.newXMLDocumentManager();
DOMHandle handle = new DOMHandle();
handle.set(document);
docMgr.write(docId, handle);

The following code uses a JacksonHandle to write a JSON document to MarkLogic. Assume node is some previously initialized in-memory JsonNode document.

JSONDocumentManager JSONDocMgr = client.newJSONDocumentManager();
JacksonHandle handleJson = new JacksonHandle();
handleJson.set(node);
JSONDocMgr.write(docId, handleJson);

For additional examples, see the examples in the following packages. The source is available on GitHub. For details, see Downloading the Library Source Code.

  • com.marklogic.client.example.cookbook
  • com.marklogic.client.example.handle

Shortcut Methods as an Alternative to Creating Handles

Shortcut methods enable you to pass supported data types directly into or out of an operation without explicitly creating a handle to reference the data. These convenience methods can make your code more readable.

For more details, see the following topics:

Understanding Shortcut Methods

Many Java Client API classes and interfaces include shortcut methods of the form operationAs, such as readAs or writeAs. These methods enable you to bypass the equivalent, more strongly typed methods that require you to pass in a handle. Using shortcut methods instead of handles can make your code more readable.

For example, the XMLDocumentManager and JSONDocument Manager interfaces includes both read and readAs methods such as the following:

// strongly typed, handle based
read(String docId, T contentHanlde)

// shortcut equivalent
readAs(String docId, Class<T> as)

This means you can read a document from the database using a call of either of the following forms:

// strongly typed, returns the populated DOMHandle object
DOMHandle handle = docMgr.read(docURI, new DOMHandle());

// shortcut, returns a DOM Document
Document doc = docMgr.readAs(docURI, Document.class);

// strongly typed, returns the populated JacksonHandle object
JacksonHandle handleJSON = JSONDocMgr.read(docURI, new JacksonHandle());

// shortcut, returns a JsonNode
JsonNode node = JSONDocMgr.readAs(docURI, JsonNode.class);

Similarly, you can use XMLDocumentManager or JSONDocumentManager to write a document to the database using either of the following calls:

// strongly typed
docMgr.write(docURI, new DOMHandle(theDocument));

// shortcut
docMgr.writeAs(docURI, theDocument);

// strongly typed
JSONDocMgr.write(docURI, new JacksonHandle(theJsonNode));

// shortcut
JSONDocMgr.writeAs(docURI, theJsonNode));

Shortcut methods are not limited to document read and write operations. For example, you can use either QueryManager.newRawCombinedQueryDefinition or QueryManager.newRawCombinedQueryDefinitionAs to create a RawCombinedQueryDefinition.

When to Choose Strongly Typed Over Shortcut

Shortcut methods are the best choice in most cases because they improve the readability and maintainability of your code. However, you should keep the following points in mind:

  • A shortcut method is not more efficient than the equivalent strongly typed method. Internally, a Handle is still created to manage the data.
  • Using a shortcut method introduces a small risk because you're side-stepping the strong typing provided by a handle. For example, an exception is thrown if there is no handle type corresponding to class type you provide to the shortcut method.

The typing exposure is limited since the Java Client API pre-defines Handle classes for a broad range of types. You can register your own class-to-handle pairings to extend the supported classes. For details, see Extending Shortcuts by Registering Handle Factories.

Consider the strongly typed call form in the following cases:

  • You want compile-time checking of input and output types.
  • You want a slight increase in efficiency over a large number of requests.
  • You need to control the MIME type or format of a handle.

Extending Shortcuts by Registering Handle Factories

Though you do not have to create a handle when using a shortcut method, the shortcut implementation still creates a Handle to manage the data.

For example, when you issue a shortcut call such as the following, the implementation creates a DOMHandle to receive the document read from the database.

docMgr.readAs(docURI, Document.class);

Similarly, the following implementation creates a JacksonHandle to receive the document read from the database.

JSONDocMgr.readAs(docURI, JsonNode.class);

This means that a shortcut method must be able to create a handle capable of handling the targeted class type. This capability is provided by a registry for handle factories. The shortcut method can query the registry for a handle factory that can process a particular class type. For details, see DatabaseClientFactory.HandleFactoryRegistry in the Java Client API Documentation.

The Java Client API automatically registers factories for the following handle classes. For details on the data types supported by each handle type, see the handle class documentation in the Java Client API Documentation.

BytesHandle InputStreamHandle SourceHandle
DOMHandle JacksonHandle StringHandle
FileHandle JacksonParserHandle XMLEventReaderHandle
InputSourceHandle ReaderHandle XMLStreamReaderHandle

If you create your own handle class, you can register a handle factory for it so that you can use shortcut methods on the classes supported by your handle class.

Handle factory registration must be completed before you create a DatabaseClient.

You can use the same mechansim with a JAXBHandle factory to enable shortcut methods on POJOs. For example, if you have a POJO class named Product, then you can add it to the registry as follows:

DatabaseClientFactory.getHandleRegistry().register(
    JAXBHandle.newFactory(Product.class);

You can also use JacksonDatabindHandle factory to enable shortcut methods on POJOs.

DatabaseClientFactory.getHandleRegistry().register(
    JacksonDatabindHandle.newFactory(Product.class);

Then you can subsequently write Product POJOs to MarkLogic and read them back as follows:

XMLDocumentManager docMgr = client.newXMLDocumentManager();
Product product = // ...create a Product

docMgr.writeAs(docURI, Product.class);
// ...
product = docMgr.readAs(docURI, Product.class);

Likewise with a JSONDocumentManager:

JSONDocumentManager JSONDocMgr = client.newJSONDocumentManager();
Product product = // ...create a Product

JSONDocMgr.writeAs(docURI, Product.class);
// ...
product = JSONDocMgr.readAs(docURI, Product.class);

Note that the Java Client API also includes a POJO data binding capability as an alternative to managing your own POJOs with JAXB. Using this feature eliminates the need for the above registration. For more details, see POJO Data Binding Interface.

Thread Safety of the Java API

You should be aware of the following API characteristics with respect to thread safety:

  • DatabaseClient is thread safe after initialization.
  • The various manager classes are thread safe after initial configuration. Examples: DocumentManager, QueryManager, ResourceManager.
  • Handles are not thread safe. Examples: StringHandle, FileHandle, SearchHandle.
  • Builders are not thread safe. Examples: DocumentPatchBuilder, StructuredQueryBuilder.

For example, you can create a DocumentManager for manipulating XML documents and share it across multiple threads. Similarly, you can create a QueryManager, set the page length, and then share it between multiple threads.

Handles can be used across multiple requests within the same thread, but cannot be used across threads, so whenever you create a new thread, you must create new Handle objects to use in that thread.

Downloading the Library Source Code

The Java API is an open source project. Though you do not need the source code to use the library, the source is available from GitHub at the following URL:

https://github.com/marklogic/java-client-api

Assuming you have a Git client and the git command is on your path, you can download a local copy of the latest source using the following command:

git clone https://github.com/marklogic/java-client-api.git

« Table of contents
Next chapter »