Sample Code

The Java SDK supports creation and handling of extended attributes.

Sub-Document Operations and Extended Attributes

A high-level summary of extended attributes can be found in Extended Attributes. Extended attributes (XATTRs) are accessed by means of extensions to the Sub-Document API.

Java Extended Attributes Example

The following code demonstrates how extended attributes can be used. It assumes that Couchbase Server is established on localhost; that the Full Administrator username and password are Administrator and password respectively; and that the travel-sample bucket is installed. For information on installing the travel-sample bucket, see Sample Buckets.

import java.util.Arrays;
import com.couchbase.client.core.message.kv.subdoc.multi.Lookup;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.cluster.UserRole;
import com.couchbase.client.java.cluster.UserSettings;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.query.N1qlQueryRow;
import com.couchbase.client.java.subdoc.*;

public class CouchbaseExtendedAttributesDemo
{
    public static void main(String [] args)
    {
        // Access the cluster that is running on the local host, authenticating with
        // the username and password of the Full Administrator. This
        // provides all privileges.
        //
        Cluster cluster = CouchbaseCluster.create("localhost");

        System.out.print("Authenticating as administrator." + "\n");
        cluster.authenticate("Administrator", "password");

        // Open the travel-sample bucket.
        //
        Bucket travelSample = cluster.openBucket("travel-sample");

        // Add key-value pairs to hotel_10138, representing traveller-Ids and associated
        // discount percentages.
        //
        travelSample.mutateIn("hotel_10138")

            .upsert("discounts.jsmith123", "20", new SubdocOptionsBuilder()
                .createParents(true).xattr(true))

            .upsert("discounts.pjones356", "30", new SubdocOptionsBuilder()
                .createParents(true).xattr(true))

            // The following lines, "insert" and "remove", simply demonstrate insertion and
            // removal of the same path and value.
            //
            .insert("discounts.jbrown789", "25", new SubdocOptionsBuilder()
                .createParents(true).xattr(true))

            .remove("discounts.jbrown789", new SubdocOptionsBuilder().xattr(true))

            .execute();

        // Add key-value pairs to hotel_10142, again representing traveller-Ids and
        // associated discount percentages.
        //
        travelSample.mutateIn("hotel_10142")

            .upsert("discounts.jsmith123", "15", new SubdocOptionsBuilder()
                .createParents(true).xattr(true))

            .upsert("discounts.pjones356", "10", new SubdocOptionsBuilder()
                .createParents(true).xattr(true))

            .execute();

        // Create a user and assign roles. This user will search for their
        // available discounts.
        //
        System.out.print("Upserting new user." + "\n");

        cluster.clusterManager().upsertUser("jsmith123", UserSettings.build()

            .password("jsmith123pwd")
            .name("John Smith")
            .roles(Arrays.asList(

                // Roles required for the reading of data from
                // the bucket.
                //
                new UserRole("data_reader", "travel-sample"),
                new UserRole("query_select", "travel-sample"),

                // Roles required for the writing of data into
                // the bucket.
                //
                new UserRole("data_writer", "travel-sample"),
                new UserRole("query_insert", "travel-sample"),
                new UserRole("query_delete", "travel-sample"),

                // Role required for the creation of indexes
                // on the bucket.
                //
                new UserRole("query_manage_index", "travel-sample")
        )));

        // As administrator, disconnect from cluster.
        //
        System.out.println("Administrator disconnecting.");
        cluster.disconnect();

        // Re-access the cluster as the created user.
        //
        System.out.print("User now connecting and authenticating."  + "\n");
        Cluster userCluster = CouchbaseCluster.create("localhost");

        String theusername = "jsmith123";
        String thepassword = "jsmith123pwd";

        userCluster.authenticate(theusername, thepassword);

        System.out.print("Opening travel-sample bucket as user."  + "\n");
        Bucket travelSampleForUser = userCluster.openBucket("travel-sample");

        // Perform a N1QL Query to return document IDs from the bucket. These IDs will be
        // used to reference each document in turn, and check for extended attributues
        // corresponding to discounts.
        //
        System.out.println("Searching for discounts, as user.");
        N1qlQueryResult result = travelSampleForUser.query(
            N1qlQuery.simple("SELECT id, meta(`travel-sample`).id " +
                "AS docID FROM `travel-sample`")
        );

        // Get the docID of each document returned, and use the ID to determine whether
        // the extended attribute exists.
        //
        String theId = "";
        String resultsReturned = "";
        String searchPath = "discounts." + theusername;

        for (N1qlQueryRow row : result)
        {
            // Get the docID of the current document.
            //
            theId = row.value().getString("docID");

            // Determine whether a hotel-discount has been applied to this user.
            //
            DocumentFragment<Lookup> whetherDiscountExistsForUser =
                travelSampleForUser.lookupIn(theId) .exists(searchPath,
                    new SubdocOptionsBuilder().xattr(true)).execute();

            // If so, get the discount-percentage.
            //
            if (whetherDiscountExistsForUser.content(searchPath, Boolean.class))
            {
                DocumentFragment<Lookup> percentageValueOfDiscount =
                    travelSampleForUser.lookupIn(theId).get(searchPath,
                        new SubdocOptionsBuilder().xattr(true)).execute();

                // If the percentage-value is greater than 15, include the document in the
                // results to be returned.
                //
                if (Integer.parseInt(percentageValueOfDiscount.content(searchPath,
                        String.class)) > 15)
                {
                    resultsReturned = resultsReturned + '\n' + travelSampleForUser.get(theId);
                }
            }
        }

        // Display the results, which features only hotels offering more than a 15% discount
        // to the current user.
        //
        System.out.println("Results returned are: " + resultsReturned);

        // Disconnect user from the cluster.
        //
        userCluster.disconnect();
    }
}

Virtual Extended Attributes Example

Using the Sub-Document API, Virtual XATTR can be used to fetch metadata about a document, via the $document virtual XATTR. A common use case is discovering documentation expiration metadata, or TTL:

import java.text.SimpleDateFormat;
import java.util.Date;

import com.couchbase.client.core.message.kv.subdoc.multi.Lookup;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.subdoc.*;

public class GetTTL
{
    public static void main(String [] args)
    {
        // Access the cluster that is running on the local host, authenticating with
        // the username and password of the Full Administrator. This
        // provides all privileges.
        //
        Cluster cluster = CouchbaseCluster.create("localhost");
        cluster.authenticate("test", "password");
        Bucket test = cluster.openBucket("test");

        String key = "Perry";

        DocumentFragment<Lookup> click = test.lookupIn(key).get("click").execute();
        System.out.println("Click: " + click.content("click"));

        DocumentFragment<Lookup> ttl = test.lookupIn(key).get("$document.exptime", new SubdocOptionsBuilder().xattr(true)).execute();
        System.out.println("ttl: " + ttl.content("$document.exptime"));

        test.touch(key, 300);

        ttl = test.lookupIn(key).get("$document.exptime", new SubdocOptionsBuilder().xattr(true)).execute();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm.ss");
        System.out.println("ttl: " + sdf.format(new Date(ttl.content("$document.exptime", int.class))));

        test.touch(key, 0);

        //Multiple paths can be access at once via subdoc. It's limited to 16 paths and xattrs have to be first
        DocumentFragment<Lookup> multiSubPath = test.lookupIn(key).get("$document.exptime", new SubdocOptionsBuilder().xattr(true))
                                                                  .get("$document.value_bytes", new SubdocOptionsBuilder().xattr(true))
                                                                  .get("click")
                                                                  .execute();

        System.out.println("Click: " + multiSubPath.content("click"));
        System.out.println("ttl  : " + multiSubPath.content("$document.exptime"));
        System.out.println("Size : " + multiSubPath.content("$document.value_bytes"));

    }
}