Keys and Queries

Keys are defined on Managed Objects using annotations. The annotation to define a key captures this information:

Here is a snippet of defining a unique unordered key on a Managed Object.

Example 5.13. Managed Object with Unique Keys

//     $Revision: 1.1.2.3 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.KeyList;
import com.kabira.platform.annotation.Managed;

/**
 * Defining a managed object with unique keys. Key fields must be declared as
 * final.
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
@Managed
@KeyList(keys =
{
    @Key(name = "ByName", fields =
    {
        "name"
    }, unique = true, ordered = false),
    @Key(name = "ByIdentifier", fields =
    {
        "identifier"
    }, unique = true, ordered = false)
})
public class Keys
{
    Keys(String name, int identifier)
    {
        this.name = name;
        this.identifier = identifier;
    }
    final String name;
    final int identifier;
}

Key fields are inherited from parent classes. Immutable key fields must be defined as non-static final. Any field type supported in Managed Objects (see the section called “Supported field types”) can be a key field. The only exceptions are:

Once a key is defined on a Managed Object, an index is maintained in shared memory for that key. This index is used to perform queries against the Managed Object by specifying the key values that should be used in the query. For example queries see the section called “Queries”.

@Key and @KeyList Annotations

Two annotations are used to define keys:

  • @Key - define a single key

  • @KeyList - define multiple keys

A single key can be defined using the @KeyList annotation, but the @Key annotation is provided to support more concise single key definitions.

Example 5.14, “@Key Annotation” shows the definition of the @Key annotation and its usage.

Example 5.14. @Key Annotation

package com.kabira.platform.annotation;

import java.lang.annotation.*;

/** This annotation defines a single key for a Managed class.
  */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Key
{
    /** The name of this key on the given type. Key names must be unique
     * for a single type (including inherited keys).
     */
    String name();

    /** An ordered list of the fields that make up this key. The fields
     * must be defined in this class or in a superclass.
     */
    String[] fields();

    /** If true, the runtime will enforce that only one instance will contain
     *  the key data.
     */
    boolean unique() default true;

    /** If true, the key data will be managed as an ordered set. */
    boolean ordered() default false;

    /** If true, the key fields can be updated. */
    boolean mutable() default false;
}

//
//    Use of @Key annotation
//
@Managed
@Key
(
    name = "ByName",
    fields = { "name" },
    unique = true,
    ordered = false,
    mutable = true
)
public class Keys
{

    Keys(String name)
    {
        this.name = name;
    }
    final String name;
}


Example 5.15, “@KeyList Annotation” shows the definition of the @KeyList annotation and its usage.

Example 5.15. @KeyList Annotation

package com.kabira.platform.annotation;

import java.lang.annotation.*;

/** This annotation is used to define multiple keys on a single Managed class.
  */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface KeyList
{
    Key[] keys();
}

//
//    Use of @KeyList annotation
//
@Managed
@KeyList(keys=
{
    @Key(name="ByName",fields={"name"},unique=true,ordered=false),
    @Key(name="ByIdentifier",fields={"identifier"},unique=true,ordered=false)
})
public class Keys
{

    Keys(String name, int identifier)
    {
        this.name = name;
        this.identifier = identifier;
    }
    final String name;
    final int identifier;
}

Key Restrictions

Managed Object keys have the following restrictions:

  • @Key and @KeyList cannot be used on the same class.

  • @KeyList must be of non-zero length (at least one @Key must be defined).

  • @Key and @KeyList can only be used on a class marked @Managed or a class extending a managed class.

  • All fields defined in each @Key annotation must exist in the class, or in a superclass.

  • All immutable key fields must be declared final and non-static.

  • Mutable key fields must be declared as non-static.

  • The key name must be unique within its class and among all inherited keys.

Key restrictions are audited when the class is loaded. If any key definitions in the class fail audit, a java.lang.ClassNotFoundException is thrown.

Inherited Keys

Key definitions are inherited by child classes. Key names must be unique in a class hierarchy. Inherited fields can be used in key definitions in child classes.

Example 5.16, “Inherited keys” shows inherited keys being used.

Example 5.16. Inherited keys

//     $Revision: 1.1.4.1 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.Transaction;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Inherited keys
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class InheritedKeys
{
    final static String Version = "version 3.14159265359";

    @Managed
    @Key(name = "ByVersion",
    fields =
    {
        "version"
    },
    ordered = false,
    unique = false)
    private static class Base
    {
        final String version;

        Base(String version)
        {
            this.version = version;
        }

        void describe()
        {
            System.out.println(getClass().getName() + " " + this);
        }
    }

    /**
     * Extend Base - inherits the ByVersion key
     */
    private static class Extension1 extends Base
    {
        Extension1(String version)
        {
            super(version);
        }
    }

    private static class Extension2 extends Base
    {
        Extension2(String version)
        {
            super(version);
        }
    }

    public static void main(String[] args)
    {
        createObjects();
        showAllObjects();
        showExtension1Objects();
        showExtension2Objects();
    }

    static void createObjects()
    {
        new Transaction()
        {
            @Override
            public void run()
            {
                new Extension1(Version);
                new Extension1(Version);
                new Extension2(Version);
            }
        }.execute();
    }

    static void showAllObjects()
    {
        new Transaction()
        {
            @Override
            public void run()
            {
                KeyManager<Base> keyManager = new KeyManager<Base>();
                KeyQuery<Base> keyQuery = keyManager.createKeyQuery(
                Base.class, "ByVersion");

                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
                keyFieldValueList.add("version", Version);
                keyQuery.defineQuery(keyFieldValueList);

                System.out.println("Select against Base");
                for (Base obj : keyQuery.getResults(LockMode.READLOCK))
                {
                    obj.describe();
                }
            }
        }.execute();
    }

    static void showExtension1Objects()
    {
        new Transaction()
        {
            @Override
            public void run()
            {
                KeyManager<Extension1> keyManager
                = new KeyManager<Extension1>();
                KeyQuery<Extension1> keyQuery = keyManager.createKeyQuery(
                Extension1.class, "ByVersion");

                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
                keyFieldValueList.add("version", Version);
                keyQuery.defineQuery(keyFieldValueList);

                System.out.println("Select against Extension1");
                for (Extension1 obj : keyQuery.getResults(LockMode.READLOCK))
                {
                    obj.describe();
                }
            }
        }.execute();
    }

    static void showExtension2Objects()
    {
        new Transaction()
        {
            @Override
            public void run()
            {
                KeyManager<Extension2> keyManager
                = new KeyManager<Extension2>();
                KeyQuery<Extension2> keyQuery = keyManager.createKeyQuery(
                Extension2.class, "ByVersion");

                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
                keyFieldValueList.add("version", Version);
                keyQuery.defineQuery(keyFieldValueList);

                System.out.println("Select against Extension2");
                for (Extension2 obj : keyQuery.getResults(LockMode.READLOCK))
                {
                    obj.describe();
                }
            }
        }.execute();
    }
}

When this snippet is run it outputs (annotation added):

//
// Querying against base returns all instances
//
[A] Select against Base
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension2 com.kabira.snippets.managedobjects.InheritedKeys$Extension2@6733d4b8
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@42bb4baf
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@d3743e75

//
// Querying against Extension1 only returns Extension1 instances
//
[A] Select against Extension1
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@42bb4baf
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension1 com.kabira.snippets.managedobjects.InheritedKeys$Extension1@d3743e75

//
// Querying against Extension2 only returns Extension1 instances
//
[A] Select against Extension2
[A] com.kabira.snippets.managedobjects.InheritedKeys$Extension2 com.kabira.snippets.managedobjects.InheritedKeys$Extension2@6733d4b8

Mutable keys

By default key values are immutable once an object has been created. However, it is possible to specify that a key value can be changed after an object has been created. This is done with the mutable field in the key annotation. If a key is modified after it has been created, the KeyManager.updateIndexes(...) method must be called after the key value is modified. If this method is not called after a key modification a runtime exception is thrown when the transaction is committed and the node is brought down.

[Note]

The KeyManager.updateIndexes(...) method does not need to be called for key values set in a constructor.

Example 5.17, “Modifying key values” shows how key values are modified.

Example 5.17. Modifying key values

//     $Revision: 1.1.2.1 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Define a managed object with a mutable key. <p> <h2> Target Nodes</h2> <ul>
 * <li> <b>domainnode</b> = A </ul>
 */
@Key(
	name = "ByName",
unique = true,
ordered = false,
mutable = true,
fields =
{
	"name"
})
@Managed
public class MutableKeys
{
	public MutableKeys(final String name)
	{
		this.name = name;
	}
	String name;

	public static void main(String[] args)
	{
		//
		//	Create an instance
		//
		new Transaction("Create")
		{
			@Override
			protected void run() throws Rollback
			{
				new MutableKeys("Jim");
			}
		}.execute();

		//
		//	Modify the key value
		//
		new Transaction("Modify")
		{
			@Override
			protected void run() throws Rollback
			{
				MutableKeys m = lookup("Jim");
				System.out.println("Name is " + m.name);

				//
				//	Update the key value
				//
				m.name = "Bob";
				KeyManager.updateIndexes(m);
			}
		}.execute();

		//
		//	Look up the object using the new key value
		//
		new Transaction("Lookup")
		{
			@Override
			protected void run() throws Rollback
			{
				MutableKeys m = lookup("Bob");
				System.out.println("Name is " + m.name);
				ManagedObject.delete(m);
			}
		}.execute();
	}

	//
	//	Lookup the object
	//
	private static MutableKeys lookup(final String name)
	{
		KeyManager<MutableKeys> km = new KeyManager<MutableKeys>();
		KeyQuery<MutableKeys> kq = km.createKeyQuery(
		MutableKeys.class, "ByName");
		KeyFieldValueList fields = new KeyFieldValueList();
		fields.add("name", name);
		kq.defineQuery(fields);

		return kq.getSingleResult(LockMode.READLOCK);
	}
}

When this snippet is run it outputs:

[A] Name is Jim
[A] Name is Bob

Duplicate Keys

It is not possible to create multiple instances of a uniquely keyed Managed Object. Creating a second instance of a uniquely keyed Managed Object will cause this exception:

com.kabira.platform.ObjectNotUniqueError

When a duplicate key exception is thrown it indicates that the object was not created.

See the section called “Atomic Create or Select” for the safe way to create uniquely keyed objects in the TIBCO ActiveSpaces® Transactions multi-threaded environment.

Example 5.18, “Duplicate Key Exception” shows the handling of a duplicate key exception.

Example 5.18. Duplicate Key Exception

//     $Revision: 1.1.2.1 $

package com.kabira.snippets.managedobjects;

import com.kabira.platform.ManagedObject;
import com.kabira.platform.ObjectNotUniqueError;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Duplicate keys
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class DuplicateKey
{
    /**
     * A managed object with a unique key
     */
    @Managed
    @Key
    (
        name = "ByNumber",
        fields = { "number" },
        unique = true,
        ordered = false
    )
    public static class MyObject
    {

        /**
         * Create MyObject
         *
         * @param number    Initialize number field
         */
        public MyObject(int number)
        {
            this.number = number;
        }
        
        final int       number;
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String [] args)
    {
        new Transaction("Duplicate Object")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create object with a key value of 1
                //
                MyObject    myObject = new MyObject(1);

                //
                //  Create another object with the same key value
                //
                try
                {
                    new MyObject(1);
                }
                catch (ObjectNotUniqueError ex)
                {
                    System.out.println(ex.getMessage());
                }

                ManagedObject.delete(myObject);
           }
        }.execute();
    }
}

When this snippet is run it outputs:

[A] Duplicate found for key 'com.kabira.snippets.managedobjects.DuplicateKey$MyObject::ByNumber'
          in com.kabira.snippets.managedobjects.DuplicateKey$MyObject, instance 1247018:5286456:2001:2. 
          Duplicate is com.kabira.snippets.managedobjects.DuplicateKey$MyObject, instance 1247018:5286456:2001:1.
          Key data:
[A] [
[A] 
[A]         number = 1
[A] ]

Queries

The following classes provide the query interface for managed objects.

  • com.kabira.platform.KeyManager<T> - Factory to create query objects.

  • com.kabira.platform.KeyQuery<T> - Query object. Used to define and execute queries.

  • com.kabira.platform.QueryScope - Support for a user-defined query scope.

  • com.kabira.platform.KeyFieldValueList - Used to define key name/value pairs.

  • com.kabira.platform.KeyFieldValueRangeList - Used to define key name/value pairs that have a range.

These classes are generally used in this order to define and execute a query.

  1. Create a KeyManager<T> object.

  2. Create a KeyQuery<T> object.

  3. Create a KeyFieldValueList object.

  4. Add key name/value pairs to the KeyFieldValueList object to define the query values.

  5. Define the query using the KeyQuery<T> and the KeyFieldValueList objects.

  6. Optionally define a user-defined query scope using a QueryScope object.

  7. Execute the query using the KeyQuery<T> object.

Once a query is defined, it can be used multiple times.

Locking and Isolation

Indexes defined by keys support a transaction isolation of SERIALIZABLE. This means that all updates to an index are transactionally isolated from all other transactions. Any modifications to an index caused by the creation or deletion of a keyed Managed Object is not visible outside of the current transaction until it commits.

The specific isolation rules for indexes are as follows:

  • Creates are always visible in the transaction in which they occur

  • Deletes are always visible in the transaction in which they occur

  • Creates are visible outside of the transaction in which they were executed after the transaction commits

  • Deletes are visible outside of the transaction in which they were executed after the transaction commits

The locking of objects returned from queries is explicit. When a query is executed the specific locking that should be applied to the objects returned from the query can be specified in the query API.

[Warning]

Non-cached remote objects returned from a query are always write locked and created in the current transaction, because they must be created on the local node before returning the result set. See Example 5.19, “Cluster Extent Query” for an example.

Query Scope

Queries are executed on the local node, a user-defined subset of nodes in the cluster, or all active nodes in a cluster. By default queries are executed on the local node and will only return objects in the local node's index.

[Warning]

When a query is executed on multiple nodes, the application must be running on all nodes on which the query is executed. Remote queries executed on nodes on which the application is not running will hang until the application is started.

Query scope is supported for:

The query scope is defined using the com.kabira.platform.QueryScope class. As mentioned, the possible query scopes are:

  • QueryScope.QUERY_CLUSTER - all active nodes in the cluster.

  • QueryScope.QUERY_LOCAL - the local node only, the default query scope.

  • A user-defined subset of cluster nodes.

When a user-defined query scope is defined using the QueryScope class, the following audits can be specified using the QueryScope.AuditMode enumeration:

  • AuditMode.AUDIT_NODE_LIST - Audit that at least one node is defined in the query scope. No other audits are performed. Inactive nodes are ignored when the query is performed.

  • AuditMode.AUDIT_DISTRIBUTION - Audit that at least one node is defined in the query scope and that distribution is active. Inactive nodes are ignored when the query is performed.

  • AuditMode.AUDIT_NODES_ACTIVE - Audit that at least one node is defined in the query scope, distribution is active, and all nodes are active. An error is reported if there inactive nodes are included in the query scope when the query is performed.

Query scopes are defined using the following methods:

  • ManagedObject.extent(...) for extent queries.

  • ManagedObject.cardinality(...) for extent cardinality.

  • KeyQuery.setQueryScope(...) for all key queries.

Example 5.19, “Cluster Extent Query” demonstrates a cluster wide extent query.

Example 5.19. Cluster Extent Query

//     $Revision: 1.1.2.4 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.QueryScope;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Managed;
import com.kabira.platform.property.Status;

/**
 * Distributed query
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainname</b> = Development
 * </ul>
 */
public class DistributedQuery
{
    @Managed
    private static class Node
    {

        public Node(String name)
        {
            this.name = name;
        }

        final String name;
    }

    /**
     * Run the distributed query snippet
     * @param args None supported
     */
    public static void main(String [ ] args) throws InterruptedException
    {
        //
        //  Create a node object in the local node
        //
        new Transaction("Create Node Objects")
        {
            @Override
            protected void run() throws Rollback
            {
                new Node(System.getProperty(Status.NODE_NAME));
            }
        }.execute();
		
		//
		//	Sleep some to let other nodes run
		//
		Thread.sleep(1000);

        //
        //  Find all node objects created in the cluster
        //
        //  N.B. - an indeterminate number of nodes will
        //  be returned from the extent query in this snippet
        //  because there is no synchronization of the object creation
        //  above across the nodes.
        //
        new Transaction("Query Objects")
        {
            @Override
            protected void run() throws Rollback
            {
                for (Node n : ManagedObject.extent(
                    Node.class,
                    QueryScope.QUERY_CLUSTER,
                    LockMode.READLOCK))
                {
                    System.out.println("INFO: Node " + n.name + lockType(n));
                }
            }
        }.execute();
    }
	
	private static String lockType(final Node node)
	{
		String lockType = " (";
		
		if (Transaction.hasReadLock(node) == true)
		{
			lockType += "READ";
		}
		
		if (Transaction.hasWriteLock(node) == true)
		{
			lockType += " WRITE";
		}
		
		if (Transaction.createdInTransaction(node) == true)
		{
			lockType += " CREATED";
		}
		
		lockType += ")";
		
		return lockType;
	}
}

When this snippet is run it outputs:

//
//   Notice that the local objects are only READ locked
//   Objects read from remote nodes are both WRITE locked and
//   CREATED in the current transaction
//
[B] INFO: Node B (READ)
[B] INFO: Node C (READ WRITE CREATED)
[B] INFO: Node A (READ WRITE CREATED)

[A] INFO: Node B (READ WRITE CREATED)
[A] INFO: Node A (READ)
[A] INFO: Node C (READ WRITE CREATED)

[C] INFO: Node B (READ WRITE CREATED)
[C] INFO: Node C (READ)
[C] INFO: Node A (READ WRITE CREATED)

Unique

Example 5.20, “Unique Query” demonstrates how to define and use a single unique key to perform a query.

Example 5.20. Unique Query

//     $Revision: 1.1.2.1 $

package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Unique key query
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class UniqueKey
{
    /**
     * A managed object with a unique key
     */
    @Managed
    @Key
    (
        name = "ByNumber",
        fields = { "number" },
        unique = true,
        ordered = false
    )
    private static class MyObject
    {

        /**
         * Create MyObject
         *
         * @param number        Initialize number field
         * @param description   Initialize description field
         */
        public MyObject(int number, String description)
        {
            this.number = number;
            this.description = description;
        }
        
        final int       number;
        final String    description;
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String [] args)
    {
        new Transaction("Unique Key")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create some keyed objects
                //
                new MyObject(1, "one");
                new MyObject(2, "two");

                //
                //  Create required query objects
                //
                KeyManager<MyObject>    keyManager = new KeyManager<MyObject>();
                KeyQuery<MyObject>      keyQuery = keyManager.createKeyQuery(
                                                    MyObject.class, "ByNumber");
                KeyFieldValueList       keyFieldValueList = new KeyFieldValueList();

                //
                //  Define the query to find object 1
                //
                keyFieldValueList.add("number", 1);
                keyQuery.defineQuery(keyFieldValueList);
                System.out.println(keyQuery);
                
                //
                //  Perform the query - the returned object has a write lock
                //
                MyObject    myObject = keyQuery.getSingleResult(LockMode.WRITELOCK);
                System.out.println("\t" + myObject.description);
                ManagedObject.delete(myObject);

                //
                //  Redefine the query to find object 2.  Clear the previous value
                //  before reusing the keyFieldValueList
                //
                keyFieldValueList.clear();
                keyFieldValueList.add("number", 2);
                keyQuery.defineQuery(keyFieldValueList);
                System.out.println(keyQuery);

                //
                //  Perform the query - the returned object has a write lock
                //
                myObject = keyQuery.getSingleResult(LockMode.WRITELOCK);
                System.out.println("\t" + myObject.description);
                ManagedObject.delete(myObject);
           }
        }.execute();
    }
}

When this snippet is run it outputs:

[A] select obj from com.kabira.snippets.managedobjects.UniqueKey$MyObject 
      using ByNumber where (number == 1); Java constructor: (none)
[A]   one
[A] select obj from com.kabira.snippets.managedobjects.UniqueKey$MyObject 
      using ByNumber where (number == 2); Java constructor: (none)
[A]   two


Non-Unique

Example 5.21, “Non-Unique Query” demonstrates how to define and use a single non-unique key to perform a query. Non-unique keys must use a for loop to iterate over all returned instances.

Example 5.21. Non-Unique Query

//     $Revision: 1.1.2.1 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Non-unique key query
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class NonUniqueKey
{
    /**
     * A managed object with a non-unique key
     */
    @Managed
    @Key(name = "ByGroup",
    fields =
    {
        "group"
    },
    unique = false,
    ordered = false)
    private static class MyObject
    {
        /**
         * A non-unique, unordered object
         * @param group			Group key
         * @param description	Description
         */
        public MyObject(int group, String description)
        {
            this.group = group;
            this.description = description;
        }
        final int group;
        final String description;
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String[] args)
    {
        new Transaction("Non-Unique Key")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create some keyed objects.  Notice that a duplicate
                //  key value is specified.
                //
                new MyObject(1, "first one");
                new MyObject(1, "second one");
                new MyObject(2, "third one");

                //
                //  Create required query objects
                //
                KeyManager<MyObject> keyManager = new KeyManager<MyObject>();
                KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery(
                    MyObject.class, "ByGroup");
                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();

                //
                //  Define the query to find all objects with a key value of 1
                //
                keyFieldValueList.add("group", 1);
                keyQuery.defineQuery(keyFieldValueList);
                System.out.println(keyQuery);

                //
                //  Perform the query - we need to use a for loop to iterate over
                //  the objects.  We take a write lock as we execute the query.
                //
                for (MyObject myObject : keyQuery.getResults(LockMode.WRITELOCK))
                {
                    System.out.println("\t" + myObject.description);
                    ManagedObject.delete(myObject);
                }
            }
        }.execute();
    }
}

When this snippet is run it outputs:

[A] for obj in com.kabira.snippets.managedobjects.NonUniqueKey$MyObject 
      using ByGroup where (group == 1) { }
[A]   second one
[A]   first one

Ordered

Ordered indexes allow sorting of selected objects in either ascending or descending order. They also support finding the minimum and maximum values in an index. Ordered queries can be done on unique and non-unique keys. They are also supported on multi-field keys. This allows the result set to be selectively ordered based on partial key values.

Table 5.1, “Sort order” summarizes the sort order for all supported Managed Object field types.

[Warning]

Lexicographical sorting is only supported for single-byte character sets. Multi-byte characters are treated as single-byte characters when sorting.

Table 5.1. Sort order

TypeSort Order
boolean and Booleanfalse before true
char and CharacterLexicographical order.
byte and ByteNumeric order.
Integer Types (short and Short, int and Integer, long and Long)Numeric order.
Floating Point Types (float and Float, double and Double)Numeric order.
java.lang.StringLexicographical order.
java.lang.DateDate order.
EnumerationsEnumeration ordinal value order.
Managed object referencesUndefined.

Example 5.22, “Ordered Query” demonstrates how to define a multi-field unique key to perform ordered queries.

Example 5.22. Ordered Query

//     $Revision: 1.1.2.1 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyOrderedBy;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;

/**
 * Unique ordered key query
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class Ordered
{
    /**
     * A managed object with a unique multi-part key
     */
    @Managed
    @Key(name = "ByGroupDescription",
    fields =
    {
        "group", "description"
    },
    unique = true,
    ordered = true)
    private static class MyObject
    {
        /**
         * A non-unique ordered object
         * @param group			Group
         * @param description	Description
         */
        public MyObject(int group, String description)
        {
            this.group = group;
            this.description = description;
        }
        final int group;
        final String description;
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String[] args)
    {
        new Transaction("Ordered Query")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create some keyed objects.  These objects are unique because
                //  both the group and description field are part of the key
                //
                new MyObject(1, "a");
                new MyObject(1, "b");
                new MyObject(1, "c");
                new MyObject(2, "d");
                new MyObject(2, "e");
                new MyObject(2, "f");
                //
                //  Create required query objects
                //
                KeyManager<MyObject> keyManager = new KeyManager<MyObject>();
                KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery(
                    MyObject.class, "ByGroupDescription");
                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();

                //
                //  Define the query to find all objects with a key value of 1
                //  Notice that no field value is specified for the description
                //  field.  This will cause all objects with a group value of
                //  1 to be sorted on the description field.
                //
                keyFieldValueList.add("group", 1);
                keyQuery.defineQuery(keyFieldValueList);
                System.out.println(keyQuery);

                //
                //  Get the maximum value in group 1
                //
                System.out.println("Maximum Group 1 Value:");
                MyObject mo = keyQuery.getMaximumResult(LockMode.READLOCK);
                System.out.println("\t" + mo.description);

                //
                //  Get the minimum value in group 1
                //
                System.out.println("Minimum Group 1 Value:");
                mo = keyQuery.getMinimumResult(LockMode.READLOCK);
                System.out.println("\t" + mo.description);

                //
                //  Perform an ordered descending query on group 1 taking
                //  a write lock
                //
                System.out.println("Descending Group 1 Query:");
                for (MyObject myObject : keyQuery.getResults(
                    KeyOrderedBy.DESCENDING, LockMode.WRITELOCK))
                {
                    System.out.println("\t" + myObject.description);
                }

                //
                //  Perform an ordered ascending query on group 1 taking
                //  a write lock
                //
                System.out.println("Ascending Group 1 Query:");
                for (MyObject myObject : keyQuery.getResults(
                    KeyOrderedBy.ASCENDING, LockMode.WRITELOCK))
                {
                    System.out.println("\t" + myObject.description);
                }

                //
                //  Repeat the queries for Group 2
                //
                keyFieldValueList.clear();
                keyFieldValueList.add("group", 2);
                keyQuery.defineQuery(keyFieldValueList);
                System.out.println(keyQuery);

                System.out.println("Maximum Group 2 Value:");
                mo = keyQuery.getMaximumResult(LockMode.READLOCK);
                System.out.println("\t" + mo.description);


                System.out.println("Minimum Group 2 Value:");
                mo = keyQuery.getMinimumResult(LockMode.READLOCK);
                System.out.println("\t" + mo.description);

                System.out.println("Descending Group 2 Query:");
                for (MyObject myObject : keyQuery.getResults(
                    KeyOrderedBy.DESCENDING, LockMode.WRITELOCK))
                {
                    System.out.println("\t" + myObject.description);
                }

                System.out.println("Ascending Group 2 Query:");
                for (MyObject myObject : keyQuery.getResults(
                    KeyOrderedBy.ASCENDING, LockMode.WRITELOCK))
                {
                    System.out.println("\t" + myObject.description);
                }

                //
                //  Remove all of the objects
                //
                for (MyObject myObject : ManagedObject.extent(MyObject.class))
                {
                    ManagedObject.delete(myObject);
                }
            }
        }.execute();
    }
}

When this snippet is run it outputs:

[A] for obj in com.kabira.snippets.managedobjects.Ordered$MyObject 
    using ByGroupDescription where (group == 1) { }
[A] Maximum Group 1 Value:
[A]   c
[A] Minimum Group 1 Value:
[A]   a
[A] Descending Group 1 Query:
[A]   c
[A]   b
[A]   a
[A] Ascending Group 1 Query:
[A]   a
[A]   b
[A]   c
[A] for obj in com.kabira.snippets.managedobjects.Ordered$MyObject 
    using ByGroupDescription where (group == 2) { }
[A] Maximum Group 2 Value:
[A]   f
[A] Minimum Group 2 Value:
[A]   d
[A] Descending Group 2 Query:
[A]   f
[A]   e
[A]   d
[A] Ascending Group 2 Query:
[A]   d
[A]   e
[A]   f

Range

The snippet shows how to perform range queries on an ordered index.

Example 5.23. Range Query

//     $Revision: 1.1.2.1 $

package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyComparisonOperator;
import com.kabira.platform.KeyFieldValueRangeList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyOrderedBy;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.Transaction;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;


/**
 * Range query
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class Range
{
    /**
     * A managed object with a unique multi-part key
     */
    @Managed
    @Key
    (
        name = "ByNumberDescription",
        fields = { "number", "description" },
        unique = true,
        ordered = true
    )
    private static class MyObject
    {

        /**
         * Create MyObject
         *
         * @param number        Initialize number field
         * @param description   Initialize description field
         */
        public MyObject(int number, String description)
        {
            this.number = number;
            this.description = description;
        }
        
        final int       number;
        final String    description;
    }

    /**
     * Main entry point
     * @param args  Not used
     */
    public static void main(String [] args)
    {
        new Transaction("Range Query")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create some keyed objects.  These objects are unique because
                //  both the number and description field are part of the key
                //
                new MyObject(1, "a");
                new MyObject(1, "b");
                new MyObject(1, "c");
                new MyObject(2, "a");
                new MyObject(2, "b");
                new MyObject(3, "a");
                new MyObject(4, "a");
                new MyObject(5, "a");

                //
                //  Create required query objects
                //
                KeyManager<MyObject>    keyManager = new KeyManager<MyObject>();
                KeyQuery<MyObject>      keyQuery = keyManager.createKeyQuery(
                                                MyObject.class, "ByNumberDescription");
                KeyFieldValueRangeList  keyFieldValueRangeList =
                                                new KeyFieldValueRangeList();

                //
                //  Define the query to find all objects with a number value < 3
                //
                keyFieldValueRangeList.add("number", 3, KeyComparisonOperator.LT);
                keyQuery.defineQuery(keyFieldValueRangeList);

                System.out.println(keyQuery);
                for (MyObject myObject : keyQuery.getResults(LockMode.NOLOCK))
                {
                    System.out.println(
                        "\t" + myObject.number + ":" + myObject.description);
                }

                //
                //  Define the query to find all objects with a number between 1 and 5,
                //  then sort them in descending order
                //
                keyFieldValueRangeList.clear();
                keyFieldValueRangeList.add("number", 1, KeyComparisonOperator.GT);
                keyFieldValueRangeList.add("number", 5, KeyComparisonOperator.LT);
                keyQuery.defineQuery(keyFieldValueRangeList);

                System.out.println(keyQuery);
                for (MyObject myObject : keyQuery.getResults(
                    KeyOrderedBy.DESCENDING, LockMode.NOLOCK))
                {
                     System.out.println(
                         "\t" + myObject.number + ":" + myObject.description);
                }

                for (MyObject myObject : ManagedObject.extent(MyObject.class))
                {
                    ManagedObject.delete(myObject);
                }
           }
        }.execute();
    }
}

When this snippet is run it outputs:

[A] for obj in com.kabira.snippets.managedobjects.Range$MyObject 
     using ByNumberDescription where (number < 3) { }
[A]  1:a
[A]  1:b
[A]  1:c
[A]  2:a
[A]  2:b
[A] for obj in com.kabira.snippets.managedobjects.Range$MyObject 
     using ByNumberDescription where (number > 1 && number < 5) { }
[A]  4:a
[A]  3:a
[A]  2:b
[A]  2:a

Atomic Create or Select

Often an application wants to atomically select a unique object if it exists, and if it does not exist, to create the instance. The query interface provides a mechanism to do this using the KeyQuery<T>.getOrCreateSingleResult method. Query scope (see the section called “Query Scope”) can be used to select or create a unique instance on only the local node, a set of nodes in the cluster, or all nodes in the cluster. The KeyQuery<T>.getOrCreateSingleResult method always returns an object to the caller.

The KeyQuery<T>.getOrCreateSingleResult method works even when multiple threads may be selecting the same instance simultaneously. If multiple threads are attempting to perform a select for an object that does not exist, one of the threads will create the new instance, and all other threads will block until the transaction in which the object was created commits. The other threads are released after the transaction completes and the select will return the new instance.

The lock mode specified in the the KeyQuery<T>.getOrCreateSingleResult method is only used if the object already exists. It is ignored if the object was created. Newly created objects always have a transactional write lock.

See the section called “Constructors” for details on constructor execution when an object is created by the KeyQuery<T>.getOrCreateSingleResult method.

Example 5.24, “Atomic Create of Unique Keyed Object” demonstrates how to locate a unique instance on the local node, either by selecting or instantiating the object. It also shows how to determine whether the object was created or selected using the Transaction.createdInTransaction(Object object) and Transaction.modifiedInTransaction(Object object) methods.

Example 5.24. Atomic Create of Unique Keyed Object

//     $Revision: 1.1.2.4 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.Transaction;
import com.kabira.platform.ManagedObject;
import com.kabira.platform.QueryScope;
import com.kabira.platform.Transaction.Rollback;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.Managed;
import com.kabira.platform.annotation.KeyField;

/**
 * Atomically creating a uniquely keyed object
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class AtomicCreate
{
    /**
     * A managed object with a unique key
     */
    @Managed
    @Key(name = "ByNumber",
    fields =
    {
        "number"
    },
    unique = true,
    ordered = false)
    private static class MyObject
    {
        /**
         * Create MyObject and initialize description
         *
         * @param aNumber Initialize number field
         * @param aDescription Initialize description field
         */
        public MyObject(
            @KeyField(fieldName = "number") int aNumber,
            @KeyField(fieldName = "description") String aDescription)
        {
            this.number = aNumber;
            this.description = aDescription;
        }

        /**
         * Create MyObject with default description
         *
         * @param number Initialize number field
         */
        public MyObject(
            @KeyField(fieldName = "number") int number)
        {
            this.number = number;
            this.description = "default description";
        }
        final int number;
        final String description;
    }

    /**
     * Main entry point
     *
     * @param args Not used
     */
    public static void main(String[] args)
    {
        //
        //    Create an object
        //
        new Transaction("Create Object")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create object with a key value of 1
                //
                MyObject myObject = new MyObject(1, "start of world");
                displayStatus("Create with constructor", myObject);
            }
        }.execute();

        new Transaction("Atomic Create")
        {
            @Override
            protected void run() throws Rollback
            {
                //
                //  Create required query objects
                //
                KeyManager<MyObject> keyManager = new KeyManager<MyObject>();
                KeyQuery<MyObject> keyQuery = keyManager.createKeyQuery(
                    MyObject.class, "ByNumber");
                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
                KeyFieldValueList additionalFields = new KeyFieldValueList();
                
                //
                //    Set the query scope to local node only (default value)
                //    This could also be set to cluster wide, or a sub-set of
                //    the nodes in the cluster.
                //
                keyQuery.setQueryScope(QueryScope.QUERY_LOCAL);

                //
                //  Define the query to find object 1 which was created above
                //
                keyFieldValueList.add("number", 1);
                keyQuery.defineQuery(keyFieldValueList);

                //
                //  Set up additionalFields
                //
                additionalFields.add("description", "create new object");

                //
                //  Atomically select or create a new object.  This will return
                //  the original object created above since a key value of 1 is
                //  being used.  The Transaction.createdInTransaction
                //  and Transaction.modifiedInTransaction methods
                //  can be used to determine whether the object was
                //  created or selected.  Also note that the 
                //  description field is the value that was set when the object
                //  was created.
                //
                //  This method always returns a result and never throws
                //  an ObjectNotUniqueError exception.
                //
                System.out.println(keyQuery);
                MyObject myObject = keyQuery.getOrCreateSingleResult(
                    LockMode.WRITELOCK, additionalFields);
                displayStatus("Original", myObject);

                //
                //    Delete the object
                //
                ManagedObject.delete(myObject);
                
                //
                //  Reset the query to use a value of 2
                //
                keyFieldValueList.clear();
                keyFieldValueList.add("number", 2);
                keyQuery.defineQuery(keyFieldValueList);

                //
                //  This will return a new object instance because no object
                //  exists with a key value of 2.  The description field is 
                //  set to the value defined in additionalFields parameter.
                //
                //  The object is created using the constructor that passes
                //  in description.
                //
                System.out.println(keyQuery);
                myObject = keyQuery.getOrCreateSingleResult(
                    LockMode.WRITELOCK, additionalFields);
                displayStatus("New Key Value 2", myObject);
                ManagedObject.delete(myObject);

                //
                //  Reset the query to use a value of 3
                //
                keyFieldValueList.clear();
                keyFieldValueList.add("number", 3);
                keyQuery.defineQuery(keyFieldValueList);

                //
                //  This will return a new object instance because no object
                //  exists with a key value of 3.
                //
                //  The object is created using the constructor without a 
                //  description parameter.
                //
                System.out.println(keyQuery);
                myObject = keyQuery.getOrCreateSingleResult(LockMode.WRITELOCK, null);
                displayStatus("New Key Value 3", myObject);
                ManagedObject.delete(myObject);
            }
        }.execute();
    }

    private static void displayStatus(final String label, final MyObject myObject)
    {
        System.out.println("==== " + label + " ===");
        System.out.println("Created    : " + Transaction.createdInTransaction(myObject));
        System.out.println("Modified   : " + Transaction.modifiedInTransaction(myObject));
        System.out.println("");
    }
}

When this snippet is run it outputs (annotation added):

#
#    Object created using new
#
[A] ==== Create with constructor ===
[A] Created    : true
[A] Modified   : true
[A] Deleted    : false
[A] Description: start of world

#
#    Select of original object, notice that
#    created is false and description is
#    initial value
#
[A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject 
    using ByNumber where (number == 1); 
    Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int)
[A] ==== Original ===
[A] Created    : false
[A] Modified   : false
[A] Deleted    : false
[A] Description: start of world

#
#    Object is deleted.  Notice deleted is true
#
[A] ==== Deleted ===
[A] Created    : false
[A] Modified   : false
[A] Deleted    : true
[A] 

#
#    Object created with getOrCreateSingleResult 
#    using constructor with description parameter
#    Notice description value was set
#
[A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject 
    using ByNumber where (number == 2); 
    Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int)
[A] ==== New Key Value 2 ===
[A] Created    : true
[A] Modified   : true
[A] Deleted    : false
[A] Description: create new object

#
#    Object created with getOrCreateSingleResult 
#    using constructor without description parameter.  
#    Notice default description value.
#
[A] select obj from com.kabira.snippets.managedobjects.AtomicCreate$MyObject 
    using ByNumber where (number == 3); 
    Java constructor: public com.kabira.snippets.managedobjects.AtomicCreate$MyObject(int)
[A] ==== New Key Value 3 ===
[A] Created    : true
[A] Modified   : true
[A] Deleted    : false
[A] Description: default description
Constructors

The KeyQuery<T>.getOrCreateSingleResult method may create a new object. To create the object a constructor is called on the object being created. Keyed objects have constructors that at a minimum must have parameters to initialize the key fields. The constructor may also take additional parameters for other application specific initialization.

The @KeyField annotation is used to map constructor parameters to object fields. If the @KeyField annotation is specified, it must be specified for all parameters in the constructor. If it is not specified for all parameters an exception is thrown at runtime.

[A] com.kabira.platform.KeyConstructor$IncompleteAnnotations: Constructor 
      public sandbox.KeyConstructor(java.lang.Integer,java.lang.String) is missing one of more KeyField annotations
[A] at com.kabira.platform.KeyConstructor.findAnnotatedConstructor(KeyConstructor.java:275)
[A] at com.kabira.platform.KeyConstructor.createObject(KeyConstructor.java:205)
[A] at com.kabira.platform.KeyConstructor.invokeConstructor(KeyConstructor.java:105)
[A] at com.kabira.platform.KeyQuery.getOrCreateSingleResult(KeyQuery.java:305)
[A] at sandbox.DoIt$1.run(KeyConstructor.java:53)
[A] at com.kabira.platform.Transaction.execute(Transaction.java:303)
[A] at sandbox.DoIt.main(KeyConstructor.java:36)
[A] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[A] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[A] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[A] at java.lang.reflect.Method.invoke(Method.java:597)
[A] at com.kabira.platform.MainWrapper.invokeMain(MainWrapper.java:49)

Here is the definition of the @KeyField annotation:

package com.kabira.platform.annotation;

import java.lang.annotation.*;

/** This annotation describes the mapping between constructor arguments 
  * and the key field that the argument sets.
  */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface KeyField
{
    /** The field name this parameter maps to.
      */
    String fieldName();
}

The @KeyField annotation is not inherited. It must be specified on every constructor that will be called when the KeyQuery<T>.getOrCreateSingleResult(...) method is used to create objects. The KeyQuery<T>.getOrCreateSingleResult(...) only attempts to execute constructors on the actual object type being created. It does not search for, or excecute, any constructors inherited from parent types.

[Warning]

If the @KeyField annotation is not specified on a constructor for an object being created using KeyQuery<T>.getOrCreateSingleResult, no constructor is called. The key fields are correctly updated, any additional fields are initialized, and the object is created, but any behavior in the constructor is not executed.

See Example 5.24, “Atomic Create of Unique Keyed Object” for an example of the @KeyField annotation.

Partitioned objects

The KeyQuery<T>.getOrCreateSingleResult method on a partitioned object, when the query scope is the local node, has different behavior depending on whether the object being selected is mapped to a partition definition on the local node (see Chapter 7, High availability for details on partitioning) or not.

When the KeyQuery<T>.getOrCreateSingleResult method is called using a local node query scope, if the key does not exist on the local node, an instance is created with the key. If the instance is mapped to partition information on the local node, the create is dispatched to the active node for the partition. If the key value is found on the active node, the instance found on the active node is returned to the caller. This is a case where a local node query scope, actually performs a remote dispatch to ensure duplicate keys do not occur.

[Warning]

Executing the KeyQuery<T>.getOrCreateSingleResult method with a local node scope for partitioned objects on a node that does not have the partition definitions, will create an instance with a duplicate key. This can cause problems at a later time, if the partition containing the duplicate key is migrated to the local node. In general, executing the KeyQuery<T>.getOrCreateSingleResult method for partitioned objects should be avoided on nodes that do not contain the partition definition.

Inherited keys

When a key is inherited by a child type in a class hierarchy it is possible that a duplicate key exception will be reported by KeyQuery<T>.getOrCreateSingleResult. This is an application defect, since the same key value is being used for different child object types with a common shared parent key. Example 5.25, “Duplicate keys with atomic create or select” demonstrates the problem.

Example 5.25. Duplicate keys with atomic create or select

//     $Revision: 1.1.2.1 $
package com.kabira.snippets.managedobjects;

import com.kabira.platform.KeyFieldValueList;
import com.kabira.platform.KeyManager;
import com.kabira.platform.KeyQuery;
import com.kabira.platform.LockMode;
import com.kabira.platform.Transaction;
import com.kabira.platform.annotation.Key;
import com.kabira.platform.annotation.KeyField;
import com.kabira.platform.annotation.KeyList;
import com.kabira.platform.annotation.Managed;

/**
 * Duplicate keys with atomic selects
 * <p>
 * <h2> Target Nodes</h2>
 * <ul>
 * <li> <b>domainnode</b> = A
 * </ul>
 */
public class AtomicCreateDuplicateKey
{
    @Managed
    @KeyList(keys =
    {
        @Key(name = "ByName",
                unique = true,
                ordered = false,
                fields =
                {
                    "m_name"
                }
        )
    })
    private static class Parent
    {
        private final String m_name;

        Parent(@KeyField(fieldName = "m_name") String name)
        {
            this.m_name = name;
        }
        
        @Override
        public String toString()
        {
            return getClass().getSimpleName() + ": " + m_name;
        }
    }

    private static class ChildOne extends Parent
    {
        ChildOne(@KeyField(fieldName = "m_name") String name)
        {
            super(name);
        }
    }

    private static class ChildTwo extends Parent
    {
        ChildTwo(@KeyField(fieldName = "m_name") String name)
        {
            super(name);
        }
    }
    
    /**
     * Main entry point
     *
     * @param args Not used
     */
    public static void main(String [ ] args)
    {
        new Transaction("create objects")
        {
            @Override
            public void run()
            {
                //
                //  Create an instance using smith as the key
                //
                ChildOne c1 = new ChildOne("smith");
                System.out.println("INFO: " + c1);

                //
                //  Uniquely select on key smith, but use type ChildTwo
                //
                KeyManager<ChildTwo> km = new KeyManager<>();
                KeyQuery<ChildTwo> kq = km.createKeyQuery(ChildTwo.class, "ByName");
                KeyFieldValueList fvl = new KeyFieldValueList();

                fvl.add("m_name", "smith");
                kq.defineQuery(fvl);

                //
                //  Duplicate key will be thrown because no ChildTwo instance
                //  exists with key smith, but attempting to create instance
                //  causes a duplicate key with ChildOne
                //
                ChildTwo c2 = kq.getOrCreateSingleResult(LockMode.WRITELOCK, null);
                System.out.println("INFO: " + c2);
            }
        }.execute();
    }
}


When this snippet is run it outputs (with annotation added):

#
# ChildOne instance created
#
INFO: ChildOne: smith

#
# Duplicate key exception when attempting to create ChildTwo with same name
#
Java main class com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey.main exited with an exception.
com.kabira.platform.ObjectNotUniqueError: Duplicate found for key  
   'com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$Parent::ByName' 
   in com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo, 
   instance 3465071219:692692576:1421967247573317000:125. 
   Duplicate is com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildOne, 
   instance 3465071219:2981644168:1421967247573317000:51. Key data:
[
    m_name = "smith"
]

    at com.kabira.platform.NativeRuntime.completeSMObject(Native Method)
    at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo.$_init_Impl(AtomicCreateDuplicateKey.java:65)
    at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$ChildTwo.<init>(AtomicCreateDuplicateKey.java:64)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at com.kabira.platform.KeyConstructor.createObject(KeyConstructor.java:245)
    at com.kabira.platform.KeyConstructor.invokeConstructor(KeyConstructor.java:104)
    at com.kabira.platform.KeyQuery.getOrCreateSingleResult(KeyQuery.java:364)
    at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey$1.run(AtomicCreateDuplicateKey.java:101)
    at com.kabira.platform.Transaction.execute(Transaction.java:484)
    at com.kabira.platform.Transaction.execute(Transaction.java:542)
    at com.kabira.snippets.managedobjects.AtomicCreateDuplicateKey.main(AtomicCreateDuplicateKey.java:104)