Tutorial: Upload from Java, Download from JavaScript

This tutorial.describes how a simple unit of data, the text ‘Hello, Java!’ can be uploaded from a Java application and retrieved with a JavaScript application. 

Upload from Java

This section lists all steps required to create a small application that store the text ‘Hello, World’ in the Appjangle cloud database.

Step 1: Download Client Libraries

First download the latest version of the Appjangle client libraries from this page.
Chose the ‘Appjangle Client with Nextweb API’. The Nextweb API is more declarative in nature than the more low-level onedb API and therefore allows in most cases to write more concise code.
If you use Maven in your project, you can also use the provided Maven dependencies to link the library to your project.

Step 2: Link Client Libraries

Create a new project in your favorite IDE. This example will make use of eclipse.
Copy the downloaded .jar file into a your project directory (e.g. ‘lib’) and add the library to the classpath/ build path.

Step 3: Write App to Upload Data

Create a new class with the name ‘Upload’. Add a Java main method to the class as follows:
import io.nextweb.Query;
import io.nextweb.Session;
import io.nextweb.jre.Nextweb;

public class Upload {

    public static void main(String[] args) {
        Session session = Nextweb.createSession();

        Query hello = session.seed().append("Hello, Java!");

        System.out.println("Created:\n"+hello.get());

        session.close().get();
    }

}
Running this application should result in an output such as the following:
Created:
node("http://slicnet.com/seed1/seed1/2/1/2/h/sd/Hello__1", 
  class java.lang.String)
You can now access the created node using the reported URI. For instance, by opening http://slicnet.com/seed1/seed1/2/1/2/h/sd/Hello__1 
in a web browser.

Download from JavaScript

This section lists all steps required to create a small application that store the text ‘Hello, World’ in the Appjangle cloud database using JavaScript.

Step 1: Download and Extract Appjangle Bootstrap

Head over to github to download the Appjangle Bootstrap project or, even better, fork it if you are familiar with github.
Extract the project and open app.html in your faverioute HTML/JS editor.

Step 2: Write App to Download Data

Replace the text // Your JavaScript here with the following application:
<body>
    <script
        src="http://appjangle.com/js/v01/appjangle/appjangle.nocache.js">
    </script>

    <script>
        window.onNextwebOnedb = function() {
            var session = Nextweb.createSession();
            var hello = session
                    .node("http://slicnet.com/seed1/seed1/2/1/2/h/sd/Hello__1");
            hello.get(function(node) {
                document.body
                        .appendChild(document.createTextNode(node.value()));
            });
        }
    </script>
</body>
Save app.html and close your editor.

Step 3: Upload App to Appjangle

Open the TextSync JAR file in the appjangle-bootstrap directory (for instance, by double clicking on the file if supported by your OS).
Drag and drop the file app.html onto the files list in the TextSync App and click [Synchronize].
Note: You will need to get an Appjangle account in order to use the TextSync App. All your uploaded applications will be stored under this account.

Step 4: Open Application

Open app.html again. In the beginning of the file, copy the URL to the right of one.upload .
Past the URL into your web browser. Add to the end of the URL .value.html and replace https with http at the beginning of the URL.
Loading the page should result in the output (also see an example deployed app here):

Hello, Java

It’s not ‘Hello, JavaScript’ since here we are loading the node created by the Java Hello, World application described above. Check out the Nextweb.io API docs to find out how to change the text stored in the Appjangle cloud to a more fitting ‘Hello, JavaScript’.
This tutorial is also published on the Appjangle blog.

Jangle Java: Upload & Load Data

This tutorial illustrates in a few simple steps how to upload and download data from a Java application into the appjangle platform.

You can find the source code of the completed steps 2 & 3 on github:

T_JangleJavaShort_Upload.java

U_JangleJavaShort_Load.java

Step 1: Link Client Libraries

To upload data to appjangle from a Java application, the onedb Java Client API will be required. Download the latests version of the ‘onedb Java client’ from the onedb downloads page:

http://cms.onedb.de/downloads

Drop the client library into a Java project of your favorite IDE or assure they are added to the classpath of your Java app in another way of your choice.

Step 2: Upload Data

Define a new Java class and add a main method. Add the following code to your app:

final CoreDsl dsl = OneJre.init();

final OneClient client = dsl.createClient();

dsl.seed(client, new WhenSeeded() {

	@Override
	public void thenDo(final WithSeedResult sr) {

		final OneNode parlor = dsl.append("My Pizza Parlor")
				.to(sr.seedNode()).atAddress("./pizzaParlor")
				.in(client);

		dsl.append("Awsome Pizza!").to(parlor).in(client);

		System.out.println("Pizza Parlor Created:");
		System.out.println("at node      : " + parlor.getId());
		System.out.println("access secret: " + sr.accessToken());

		dsl.shutdown(client).and(WhenShutdown.DO_NOTHING);
	}
});

Running the application should result in an output such as the following.

Pizza Parlor Created:
at node      : https://u1.linnk.it/lnzvwp/sd1/3/h/sd/pizzaParlor
access secret: rfa______1rd

Important: Save the node and access secret for step 3.

The application above defines a number of interconnected data items on the appjangle platform:

  1. a seed root node is created by the call to the seed operation.
  2. a node with the value ‘My Pizza Parlor’ is appended to this seed node at the address ./pizzaParlor (where . denotes the seed root node)
  3. a node with the value ‘Awsome Pizza!’ is appeneded to the ‘pizza parlor’ node.

Each of these data items and their interconnections are uploaded to the appjangle platform, and, using their respective identifiers (URIs), they can be accessed from any Internet connected device using various mechansims:

  1. If the device supports Java and/or JavaScript, the data itmes can be accessed using the onedb client libraries.
  2. Per REST in various formats, for instance:

    1. XML: pizzaParlor/Awsome_Piz0.value.xml
    2. JSON: pizzaParlor/Awsome_Piz0.value.json
    3. HTML: pizzaParlor.node.html

Step 3: Load Data

While new data was defined on the appjangle platform in the previous step, in this step, the defined data is loaded and extended.

For this, define a new Java class with the following main method. Replace the node and access secret with the ones you have saved in the previous step:

final CoreDsl dsl = OneJre.init();

final OneClient client = dsl.createClient();

dsl.load("[your node]").withSecret("[your secret]")
		.in(client).and(new WhenLoaded() {

			@Override
			public void thenDo(final WithLoadResult<Object> lr) {

				final OneNode servings = dsl.append("servings")
						.to(lr.loadedNode()).atAddress("./servings")
						.in(client);

				dsl.append("Pizza!").to(servings).in(client);

				System.out.println("Servings defined.");

				dsl.shutdown(client).and(WhenShutdown.DO_NOTHING);
			}
		});

Running the app, will result in the following:

  1. The previously defined ‘pizza parlor’ node is loaded into a local client context.
  2. A node ‘servings’ is appended to the pizza parlor node at the specific address ./servings.
  3. One value “Pizza!” is added to the servings node.
  4. The local client context is closed using the shutdown operation and all changed data is uploaded to the appjangle platform.

Browsing the the pizza parlor node will now show two nodes attached to this node, the descriptive node ‘Awsome Pizza’ as well as a node containing the servings such as the following:

http://u1.linnk.it/lnzvwp/sd1/2/h/sd/pizzaParlor

Learning More

Please check the second part of this tutorial:

Jangle Java: Synchronization

You can also find more information on various aspects touched upon in this tutorial on the onedb documentation page.

Jangle Java: Synchronization

This tutorial is a continutation of the ‘Jangle Java: Upload & Load‘ tutorial. Make sure to follow the steps of this tutorial first before doing this one.

This tutorial shows the synchronization capabilities of the appjangle Java client. In particular, the well known producer-consumer scenario is applied on the simple pizza parlor data defined in the upload & load tutorial.

You can find the source code of the completed tutorial on github: V_JangleJavaShort_Synchronize.java.

Step 1: Define the Producer

The producer in this simple scenario adds nodes with the value "Pizza!" to the ./servings node defined for the pizza parlor.

For this, define a new Java class Producer as shown below. Please replace the pizza parlor node path with the path to the pizza parlor data created in the upload & load tutoria and replace the access secret as well.

static class Producer implements Runnable {

	final CoreDsl dsl = OneJre.init();
	final OneClient client = dsl.createClient();

	@Override
	public void run() {
		try {
			Thread.sleep(600);
		} catch (final InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("Making pizza ...");
		dsl.load(
				"[your node]/servings")
				.withSecret("[your secret]").in(client)
				.and(new WhenLoaded() {

					@Override
					public void thenDo(final WithLoadResult<Object> sr) {

						dsl.append(new String("Pizza!"))
								.to(sr.loadedNode()).in(client);

						dsl.commit(client).and(WhenCommitted.DO_NOTHING);

						System.out.println("... pizza made!");
						run();

					}

				});

	}

}

Calling the run() method of this class will start a recursive loop in which nodes with the value “Pizza!” will be appended to pizzaParlor/servings. The resulting changes to the local client data will be uploaded to the server via the commit operation.

Step 2: Defining the Consumer

The consumer in this scenario will ‘monitor’ the pizza parlor’s servings node for any changes. If a change occurs, the consumer will replace all “Pizza!” values with outcries of yummy!

Define a new class Consumer as follows (again replace address and access secret!):

class Consumer implements Runnable {

	private volatile int eaten = 0;

	@Override
	public void run() {
		final CoreDsl dsl = OneJre.init();
		final OneClient client = dsl.createClient();

		dsl.monitor(
				dsl.reference("[your node]/servings"))
				.inInterval(Interval.FAST).withSecret("[your secret]")
				.in(client).and(new WhenNodeChanged() {

					@Override
					public void thenDo(final WithNodeChangedContext context) {

						checkForPizza(dsl, client, context);

					}

				});
	}

This class so far will install a monitor on the ./servings node and call a checkForPizza method every time a change has been made to the servings node.

The checkForPizza method can be defined as follows:

private void checkForPizza(final CoreDsl dsl, final OneClient client,
		final WithNodeChangedContext context) {
	dsl.selectFrom(context.changedNode()).theChildren()
			.ofType(String.class).in(client)
			.and(new WhenChildrenSelected<OneTypedReference<String>>() {

				@Override
				public void thenDo(
						final WithChildrenSelectedResult<OneTypedReference<String>> csr) {

					for (final OneTypedReference<String> pizza : csr
							.children()) {

						if (!dsl.dereference(pizza).in(client)
								.equals("Pizza!")) {
							continue;
						}

						dsl.replace(pizza)
								.with("Yummy (" + eaten + ")!")
								.in(client);
						eaten++;
						System.out.println("Ate " + eaten
								+ " Pizza(s)!");
					}

					dsl.commit(client).and(WhenCommitted.DO_NOTHING);

					if (eaten > 10) {
						System.out.println("Had enough pizza.");
						context.monitor().stop(WhenShutdown.DO_NOTHING);
						System.exit(0);
					}
				}
			});
}

This method does the following:

  1. All child nodes, which have the class String.class are selected from the pizzaParlor/servings node through calling the method selectFrom(...).
  2. All children of the class String are checked whether their value equals “Pizza!”.
  3. If so, the respective value is replaces with the value “Yummy (x)!”.
  4. Lastly, all changes made to the servings node are committed to the appjangle cloud.

Producer and consumer can be assembled in an application such as given here. Running this application should result in an output such as the following:

Making pizza ...
Ate 1 Pizza(s)!
... pizza made!
Making pizza ...
... pizza made!
Making pizza ...
... pizza made!
Ate 2 Pizza(s)!
Ate 3 Pizza(s)!
Making pizza ...
... pizza made!
Making pizza ...
Ate 4 Pizza(s)!
... pizza made!
Making pizza ...

While such a producer/consumer example is easily accomplished using threads and shared resources, the producer and consumer in the given example need not be part of one application. Indeed, producer and consumer can run on seperate Java-compatible devices as long as these are connected to the Internet.

The work of producer and consumer can further be traced through the various REST interfaces as seen for instance on the following page:

http://u1.linnk.it/lnzvwp/sd1/2/h/sd/pizzaParlor/servings.node.html

What’s more?

To learn more about the capabilities of appjangle and onedb, you can check:

onedb 0.0.6 powers appjangle

The last few weeks a great number of improvements and changes have been made to the onedb database. Chiefly, with the development of the JavaScript API, onedb has been integrated into the new appjangle platform.

This platform is designed to enable not only to store data from applications from various platforms but also to aid you in developing and deploying these applications. Please note that the appjangle platform is currently in PREVIEW and various features are in the process of being completed.

Currently, all already documented features of onedb are available on appjangle. However, it is not possible anymore to obtain an API key from the onedb website. Instead, signing up for appjangle will get you an API key you can use to develop Java applications.

There is a new, short tutorial helping to get started developing Java apps with appjangle/onedb. Check out:

Jangle Java: Upload & Load Tutorial

Jangle Java: Synchronization Tutorial

New Features

Here a short list of the additional capabilities of the onedb engine in version 0.0.6:

Seed operation

The seed operation has been added as further element operation to the client API. The seed operation allows creating a root node on the appjangle platform. This root node is meant as a space for a small amount of data, required if it is not possible to connect to a registered user’s database. It’s great for creating a space to try out the various features, too.

Currently, it is encouraged to limit nodes appended to a seed node to about 50 nodes. All nodes appended to a seed are by default protected by a private write secret but they do allow public read.

If more data is to be uploaded or the data should also be private read protected, the createRealm operation can be used.

A usage example for the seed operation can be found in the following Java App:

RapidDataUploadFromAnywhere.java

Binary Nodes

It is now possible to upload nodes with binary content such as images to the appjangle platform.

You can create a new node with binary content as follows:

byte[] myData = ...
OneBytes node = one.common.nodes.newByteData(myData, "image/png");
One.append(node).to(parent).atAddress("./mypic").in(c);

You can then access this picture through the REST server, for instance:

http://u1.linnk.it/xxzv0z/imgTst/odblogo.value.png

Note that you will need to add “.value.png” to the nodes address to assure it is rendered as binary data. Other currently supported formats for binary data are:

  • .value.jpg
  • .value.jpeg
  • .value.ico
  • .value.gif

Markdown Serialization

Text nodes, which contain content in the Markdown format, can now be rendered into HTML automatically. Just add the marker One.reference("https://u1.linnk.it/6wbnoq/Types/isMarkdown") to your plain text node.

A sample Java app is available on github:

ServerSideMarkdownRendering.java

Template Rendering

A simple syntax allows now to embed text nodes within other text nodes. This is a feature heavily utilized for the appjangle JavaScript source files.

See the sample Java app on github, which illustrates the template feature:

EmbeddingTextNodes.java

Using a slight variation of the syntax, it is also possible to embed HTML rendered from a Markdown template into a HTML page. The syntax for this is:

<!-- embedMarkdown("[path to node]") -->

User Registration/Login

The onedb client engine now allows to interface with the user login and registration of the appjangle platform. For this, the two operations registerUser(..) and loginUser(..) have been added to the core API.

These operations do not only allow to register and login a user for the appjangle platform but also build login/registration capabilities for your own apps. These operations require to specify a applicationNodeUri. This can be an address to any node on the appjangle platform. The specified node can then function as a global unique identifier for your application and associated registered users.

Have a look at the appjangle login module and the appjangle signin module (you will need to check the page’s source) for examples of usage of this API. The Java API works very similar to the JavaScript version and examples for this will be added shortly.

Bug Fixes

A number of bugs in the onedb core engine have been fixed. Chiefly:

  • References to nodes stored on other server instances would sometimes not be resolved correctly. To fix this issues, all references are always assumed to be resolvable. In consequence, it is not permitted anymore to add a reference object to a network, if there is no resolvable node defined for this reference.
  • Fixed a bug in the authorization system. If a child node would have a matching security token with insufficient authorization but the parent node would have a security token with sufficient authorization, the token in the parent node would not be recognized.
  • A number of stability improvements have been made to the routines rendering HTML nodes.

onedb 0.0.4

The version 0.0.4 of the core client library and the test library for onedb have been released. In addition to a small enhancement and bugfix also some documentation has been added. Please find the details below.

The development of onedb has made extensive use of the OSGi platform to assure that the library is truly modular and no unwanted interdependencies between different components ‘creep’ into the codebase. I use OSGi also in other projects and, since it is often difficult to find OSGi ready versions of Maven artifacts, I have uploaded a number of such artifacts on github.

Apart from the smaller changes made to the client libraries, some bigger changes are in the works on the server-side. In particular, a number of changes have been made to accommodate the almost ready JavaScript API. I will write a separate post about these changes once they have been fully tested.

All older client libraries (0.0.1-0.0.3) should continue to function. No API breaking changes have been introduced in 0.0.4 (for apps written against the API of 0.0.3).

Documentation

There is now a small Java swing example application available on github. At the moment, only two screens are implemented:

  • Screen 1: Provides a simple user interface to create new realms (note, you will need to obtain an API key from the onedb website for this feature)
  • Screen 2: Provides a simple user interface to upload node with text to onedb.

Enhancements

  • It is now possible to ‘identify’ security tokens with the URI of the node, for which they grant access. This helps client and server negotiate the correct required secret for a node. For instance:

one.newNode(“mysecret”).asReadTokenWithIdentification(“http://u1.linnk.it/erg/myrealm“);

Bugfixes

  • The pom.xml file for oneTestJre.min and oneClientJre.min could lead to problems in some cases, since they included a dependency to the internal artefact oneClientJre. The pom.xml files for the 0.0.4 artifacts resolve this problem.

Threads in GWT?

While it is widely reported that Google Web Toolkit does not support Java Threads and multi-threading, a number of aspects of concurrent applications can be emulated in GWT’s single thread JavaScript world.

The particular features that are relatively easy to emulate are:

  • Timers
  • Background Execution
  • Locks
  • ‘Thread-safe’ Collections

An important theme in my implementation of onedb was to write code in Java, which can be used both in a JVM environment and a GWT/JavaScript environment. Unfortunately, even though the Java Concurrency API and GWT’s concurrency features often provide similar features, their APIs are incompatible.

For instance, a codebase, which uses a java.util.Timer, cannot use a com.google.gwt.user.client.Timer at the same time.

This motivated me to write a simple abstract API (oneUtils), into which implementations for either a JVM environment or a GWT environment can be injected. Code, which uses this abstract API, can therewith be shared between JVM and GWT apps.

The abstract API currently supports the following features:

public interface Concurrency {

    public abstract TimerFactory newTimer();

    public abstract ExecutorFactory newExecutor();

    public abstract void runLater(Runnable runnable);

    public abstract OneLock newLock();

    public abstract CollectionFactory newCollection();

}

An implementation for JVM environments is included in the library (JreConcurrency), an implementation for a GWT environment is provided in the following gist:

Gist: Default Implementation of Concurrency API in GWT

Below a few usage examples for the API:

Usage of Executor API
Usage of Timer API
Usage of Thread-Safe Collections API

Please feel free to use the API + implementations. You can either grab the project from github or link to the project via Maven:

Dependency:

<dependency>
    <groupId>one.utils</groupId>
    <artifactId>oneUtils</artifactId>
    <version>0.0.3</version>
</dependency>

Repository:

<repositories>
    <repository>
        <id>onedb Releases</id>
        <url>http://dl.dropbox.com/u/957046/onedb/mvn-releases</url>
    </repository>
</repositories>

onedb 0.0.3: Client API and Documentation Updated

A new version for the onedb client libraries (0.0.3) is now available for direct download or as maven dependency.

The new versions will work with any API key obtained from www.onedb.de.

onedb Java Test Toolkit:

    <dependency>
        <groupId>one.test.jre</groupId>
        <artifactId>oneTestJre.min</artifactId>
        <version>0.0.3</version>
    </dependency>

onedb Java Client:

    <dependency>
        <groupId>one.client.jre</groupId>
        <artifactId>oneClientJre.min</artifactId>
        <version>0.0.3</version>
    </dependency>

onedb release repository (in <project>):

    <repositories>
        <repository>
            <id>onedb Releases</id>
            <url>http://dl.dropbox.com/u/957046/onedb/mvn-releases</url>
        </repository>
    </repositories>

API Improvments

This release primarily implements feedback from Michael received by email (Thanks!).

Michael has pointed out:

The name of the ShutdownCallback class does not ‘fit in’ with the general naming pattern
for callbacks (When*).

This irregular naming pattern is of course no instance of a well-designed API! To remedy this incoherence, the ShutdownCallback has been renamed to WhenShutDown and the primary callback method has been renamed to the usual thenDo(...).

The select operation returns a list of strings when all children are selected but a list
of references in case the children are filtered according to some criteria.

The select operation returned a list of strings primarily for performance reasons. Thanks to some design decisions for engine and API, the list of children returned by the
select operation is a direct reference to the internal collection of ArrayLists, which is used as a local cache by the onedb client (wrapped, of course, in a unmodifiable list). This allows to access the list of child references with very little memory and CPU overhead.

However, premature optimization is the root of all … not good things.
Therefore, the default return value for the select operation has now been changed to a list of references, which is not only aligned with the other select operations but also allows to use the results of the select operation without having to wrap the returned string values in a One.reference(...) call.

Moreover, the option for good performance must not be abandoned. I have added a new operation variant One.select(...).allChildrenFast(), which allows to access a list of strings in the same way the One.select(..).allChildren() operation did in versions 0.0.1 and 0.0.2.

Documentation

All examples and tutorials have been updated to reflect the API changes in version 0.0.3.

A new document has been added to the documentation, which explains the various representations of nodes in onedb.

Various little changes have been made to the onedb tutorial to make some of the more complex issues easier to understand.

Bugfixes

The signature of the One.reference(..) was incorrect and did not allow to obtain the references of generic objects. The method signature has been fixed.

Compatibility

Clients built with version 0.0.1 and 0.0.2 will continue to work with the onedb cloud without need for modification.

A Practical Guide on Node Types in onedb

onedb is at its heart a cloud-based platform to connect pieces of information from various applications. These ‘pieces of information’ are represented and accessed in various ways in the onedb API.

The article “onedb Architecture and Design” provides a conceptual definition of most ways in which these ‘pieces of information’ are represented and the article “onedb Tutorial: Getting Started and First Steps” uses the various representations extensively.

In addition to the two articles mentioned above, this article provides a practical guide to understand and use the various data representations in onedb. In particular, the following four representations are discussed:

  1. Nodes
  2. Addresses and Identities
  3. References
  4. Value Nodes

1. Nodes

Nodes are the central component of onedb’s data model. Basically, every piece of information be it a basic data type such as String, Integer, etc. a custom object (new MyPerson()) or onedb object (One.newNode(..)) is managed as a node by the onedb engine.

However, only those objects are nodes, which have been appended and or loaded using the onedb API. For instance, in the following example text1 is not a node while text2 is:

String text1="I am just another string";
String text2="I will be a node";

One.append(text2).to(root).in(client);

Every node managed by a onedb client has got an address and an identity as described in the following section.

2. Addresses and Identities

Nodes in onedb are identified by a global unique identifier. These global unique identifiers are encoded as resolvable
Uniform Resource Identifiers (URIs).

Hence, every piece of information in onedb has a unique identity, which can be expressed in form of a resolvable address.

Examples for such resolvable addresses usually look as follows:

https://u1.linnk.it/4hxdr8/query/bob
https://u1.linnk.it/zednuw/types/customer
https://u1.linnk.it/4hxdr8/query/This_is_a_1

3. References

Most operations in the One API do not work with addresses directly but use so called references. A reference is nothing more than a simple wrapper object around an address.

onedb favors the usage of addresses ‘wrapped’ in reference objects over simply supplying an address in form of a String object to distinguish objects, which by chance hold a value which ‘looks like’ an URI, from objects, which are meant to represent links to entities within onedb.

Given an address in text form, it is very easy to create references…

OneTypedReference<?> reference1 = One.reference("https://u1.linnk.it/4hxdr8/query/bob");   
OneTypedReference<?> reference2 = One.newNode("https://u1.linnk.it/4hxdr8/query/bob").asReference();

// reference1.equals(reference2) == true

… and to obtain the address, which is wrapped by a reference object:

String address1 = reference1.getId();

// address1.equals("https://u1.linnk.it/4hxdr8/query/bob") == true

Moreover, for every object that has been added to a onedb client, a reference can be determined:

String text1="I will be a node";

One.append(text1).to(lr.root()).in(client);

OneTypedReference<Object> ref 
               = One.reference((Object) text1).in(client);

Note in the example above that the text1 object has been cast to Object in the invocation of the One.reference(..) operation. This is necessary for the API to know that no address but a generic object is passed to the operation. For other types of objects (e.g. Integer) this explicit cast to Object is not necessary.

References can also be used to obtain the resolved object of a node. For instance, the resolved object of the node text1 in the example above would be “I will be a node”. The resolved object of a node given its reference can be obtained as follows:

Integer value1=42;

One.append(value1).to(lr.root()).in(client);

OneTypedReference<Integer> ref 
                = One.reference(value1).in(client);

Integer value = One.dereference(ref).in(client);
// value.equals(42) == true

4. Value Nodes

While virtually any Java object can become a node (given the object’s class implements the Serializable interface), there is one special kind of object, which can be added to a onedb client: value nodes.

These objects are special in that they ‘know’ their own address. For instance, the object value1 above is of the class Integer and there is therefore no possibility this object would know its own address; since the class Integer is final in Java and no additional methods/attributes can be defined for instance of this class.

The interface OneNode is used to denote classes which have knowledge of their own address. This interface defines a single method getId(), which will return the address of the node/object. As it can be seen above, the mentioned reference objects belong to this type of objects.

Value nodes are a special type of object within the OneNode category. They are nodes, which implement the interface OneValue<Type extends Serializable> These nodes, apart from knowing their own address, define a value object of any type implementing the Serializable interface. This object can be accessed using the method getValue().

In the following example, a value node bob is created and its two properties address and decorated object are accessed:

OneValue<String> bob = 
        One.newNode("bob").at("https://u1.linnk.it/4hxdr8/query/bob");
bob.getId(); // == "https://u1.linnk.it/4hxdr8/query/bob"
bob.getValue(); // == "bob" 

Value nodes can turned into back and forth from reference to resolved object like any other object:

OneTypedReference<OneValue<String>> ref 
                             = One.reference(bob);

OneValue<String> value = One.dereference(ref).in(client);
value.getValue(); // == "bob"

Note that the call to One.reference(..) for the value node is made without the addition .in(client) as has been done above for the String and Integer objects. The client does not need to be specified for determining the reference of any object, which by itself ‘knows’ its own address.

It is usually a good practice to wrap objects whenever possible into value nodes. This allows for better performance, since the onedb engine does not have to derive the address of nodes from their object identity. Since object identities can also change in often unexpected ways, it is also usually safer to work with value objects.

The easiest way to define value nodes is generally by defining the .atAddress(...) parameter when appending a new object to an existing node. For instance, in the following example a new value node with the value alice is appended to the node root with the address ./alice relative to the root node’s address:

One.append("alice").to(root).atAddress("./alice").in(client);

Given the root node is defined at the address http://u1.linnk.it/example/root, this append statement would result in the creation of the following node arrangement:

One.value("root").at("http://u1.linnk.it/example/root")
--> One.value("alice").at("http://u1.linnk.it/example/root/alice")

Conclusion

This article has discussed the various ways in which data is represented and accessed using the onedb client. Essentially, all data is encoded in form of Java objects:

  • Any Java object can be turned into nodes by appending it to node in a onedb client.
  • Java objects, which are managed as nodes, have a globally unique address.
  • This address can be used to define a lightweight (and portable) reference to the Java object.
  • References can used to resolve the original defined Java object.
  • Java objects can be wrapped in special value nodes, which allow the onedb engine to manage these objects more effectively and securely.

onedb 0.0.2: Update for Client Libraries

I have just uploaded new onedb client libraries with version number 0.0.2.

The new client libraries mainly incorporate an advice from Java API expert Lukas Eder (check out JOOQ – a neat way to access SQL databases using a fluent Java API): All callback methods in the core API now have only one parameter; a simple data object, which allows access to the information, which before was handed to the callback method in form of parameters. For instance, the create realm operation in version 0.0.1 had the following primary callback method:

@Override
public void thenDo(OneClient client, OneNode realmRoot, String secret, String partnerSecret) { 
...

In version 0.0.2, the callback method for the create realm operation has been changed to contain only one parameter:

@Override
public void thenDo(WithRealmCreatedResult r) {
...

The individual parameters (client, realmRoot, secret, partnerSecret) can now be accessed through the WithRealmCreatedResult object:

client       : r.client()
realmRoot    : r.root()
secret       : r.secret()
partnerSecret: r.partnerSecret()

This has numerous advantages:

  • Since callback methods are defined very often in code using onedb, source code size is reduced: instead of listing all parameters and their types, only callback result objects have to be defined in the method signatures.
  • This reduction in source code, plus the avoidance of (unnecessary) type information increases the readability of the source code.
  • The API is much easier to change without breaking client code; since methods/data can be added to the *Result objects without having to change the callbacks in the client code.

The tutorial has been updated to reflect the changes of the revised API.

All clients using the API version 0.0.1 will continue to work.

onedb Tutorial: Getting Started and First Steps

onedb is the database engine at the heart of the appjangle platform. This tutorial gives an overview of a number of capabilities of the onedb engine.

The following topics are discussed in this tutorial:

Part I: Set Up

  1. Getting an API key
  2. Download the client library
  3. Linking the client library (eclipse or maven)
  4. Initializing onedb engine

Part II: Core Operations

  1. Create Realm
  2. Interlude: REST access using web browser
  3. Load
  4. Append
  5. Select
  6. Replace
  7. Remove

Part III: Advanced Operations

This part is still work in progress 🙂

  1. Commit (TBA)
  2. *Safe (TBA)
  3. Monitor (TBA)
  4. Post (TBA)
  5. Managing Authorizations (TBA)
  6. Clear Versions (TBA)

For further information about the motivation, concepts and design behind onedb, please check out the other articles on the onedb website.

Part I: Set up

1. Getting an API key

onedb is a cloud based database and therefore requires a cloud service provider to work.

Currently, onedb is deployed as part of the appjangle platform.

Just head to the appjangle page and sign up. You will receive an email with a key for appjangle. You can use this key to initialize the onedb engine.

2. Download the client library

Although onedb provides a simple REST interface, far superior usability and performance is provided by the supplied client libraries. You can download the latest version so the client libraries at

http://cms.onedb.de/downloads

Note that there are two different client libraries:

onedb Java Client: This client library provides all base features required to connect with the onedb cloud.

onedb Java Test Toolkit: Apart from all features of the onedb Java Client, the test toolkit includes a stripped down version of the onedb cloud. This allows to start up a local test cloud for unit tests (starting a test cloud should take less than 200ms).

Both libraries do not have any dependencies and are minified for reduced application size and optimal performance.

For this tutorial, pleas download the most recent version of onedb Java Test Toolkit from the downloads page. If you want to deploy an application to your users, please link the onedb Java Client instead.

3. Linking the client library (eclipse or maven)

The onedb client libraries can be linked to any Java application by adding them to the Java application’s classpath. Since this is rarely done manually, I will instead briefly describe how the library can be linked to an eclipse project or added to a Maven project. If you are already familiar with these procedures, feel free to skip to the next section.

Eclipse (Windows)

Link onedb to a new eclipse project by following the steps below:

  1. Download the onedb Java Test Toolkit library as discussed above and store the ‘oneTestJre.min-x.x.x.jar’ file on your local machine.
  2. You will need to download and extract a suitable eclipse distribution. The Eclipse IDE for Java Developers will do fine to begin with.
  3. Start eclipse and create a new Java project through Menu File / New / Java Project

Create a new eclipse project

  1. Provide a ‘Project name’ such as “exploreonedb” and press [Finish]
  2. Drag and drop the file ‘oneTestJre.min-x.x.x.jar’ onto the project you have just created in the eclipse ‘Package Explorer’

Drop JAR file

  1. Select ‘Copy files’ when prompted for a ‘File Operation’
  2. Expand the “exploreonedb” project on the eclipse workspace by clicking the small triangle left to the project name.
  3. Right click the file ‘oneTestJre.min-x.x.x.jar’ in the project folder and select Build Path / Add to Build Path in the context menu

Add jar to classpath

Now you can start using the onedb client library in the eclipse project. Go to step 4 to continue.

Maven

You can link the onedb client libraries to your Maven projects by using the following dependency declarations (in <dependencies>):

For onedb Java Test Toolkit

    <dependency>
        <groupId>one.test.jre</groupId>
        <artifactId>oneTestJre.min</artifactId>
        <version>x.x.x</version> <!-- replace with most recent version -->
    </dependency>

For onedb Java Client:

    <dependency>
        <groupId>one.client.jre</groupId>
        <artifactId>oneClientJre.min</artifactId>
        <version>x.x.x</version> <!-- replace with most recent version -->
    </dependency>

You will also need to add the onedb release repository (in <project>):

    <repositories>
        <repository>
            <id>onedb Releases</id>
            <url>http://dl.dropbox.com/u/957046/onedb/mvn-releases</url>
        </repository>
    </repositories>

4. Initializing the onedb engine

After adding the onedb libraries to a Java project, the onedb engine must be initialized. Adding the following statement anywhere in your application will initialize the onedb engine and prepare the engine to make request to the onedb cloud:

OneJre.init("[Your API Key]");

Please note that you will need to replace [Your API Key] with the API key you have obtained by email as described above.

If you have linked the onedb Java Test Toolkit, you can initialize the onedb engine as above (OneJre.init(..)) but also have the option to initialize the engine in test mode. The test toolkit can recreate a local version of the onedb cloud on a per test case basis. Just add the following statement instead of the statement listed above to any of your JUnit, TestNG, … test cases:

OneTestJre.init();

Note that since this will create a local onedb cloud, you will not need to supply an API key. Also note that the local onedb cloud will not respond to any REST requests for any created resources (for performance and portability reasons).

Part II: Core Operations

onedb uses only a handful of core operations to compose flexible and expressive data structures. The operations described in the following are a comprehensive list of the core operations supported by the onedb engine.

1. Create Realm

Realms are essential in working with onedb. Every item of data stored in the onedb cloud must be part of a realm. You can check the article “onedb Architecture and Design” to learn about the design concepts behind realms. This tutorial will walk through the practical steps necessary to create realms using the onedb Java API.

If you have followed the steps above to link the downloaded library in eclipse, you will have an empty eclipse project, including a link to the library ‘oneTestJre.min-x.x.x’. I will describe the following steps using eclipse in detail. If you are an eclipse whizz or use another IDE (or no IDE at all), please feel free to skip any of following steps.

You can add a Java file by right-clicking on the ‘src’ folder in your project and selecting New / Class. Add a new class ExploreOneDb as follows:

Create a new Java class

Add the statement OneJre.init(..) with your API key.

Initialize onedb engine

The main entry point to interact with the onedb client library is the class one.common.One. You can see all fundamental operations the onedb client library provides by typing One. and waiting for the context help to appear (below a screenshot for eclipse but the same will work in IntelliJ and NetBeans as well).

Context help for One class in eclipse

Chose the option createRealm(String withTitle):

Option Select realm selected

The onedb client now requires the specification of a title of the realm, which is to be created. Realm titles have no special significance apart from that they help to build descriptive URIs for any nodes stored in the realm. Chose the title "exploration" and add a . after the closing brackets to see the context options for the createRealm(..) operation:

Context options for Create Realm

We can either specify a client session by choosing the .in(OneClient client) option or specify the operation, we would like to perform after the realm has been created successfully by choosing the option .and(RealmCreated callback). Since there is no existing client session, we chose the second option.

The parameter type RealmCreated or more precisely When.RealmCreated will help to define a so called callback for the operation. A callback defines a method, which will be called once a remote operation is completed. Callbacks are used throughout the onedb APIs for all operations, which depend on remote systems.

Realms are managed by the onedb cloud and, therefore, the operation of requesting a realm needs to make a call to the remote onedb cloud. The duration of this call can vary significantly depending on your network connection and the way messages are sent through the Internet. In any case, the call to the remote cloud takes, in terms of computer time, very long. Using a callback enables your application to do some other useful work while it is waiting for the response from the onedb cloud.

To define the callback for the createRealm(..) operation, chose the option and(..) and type within the brackets after and: new When.. This will show the available callbacks in the onedb API. Select the callback definition, which corresponds to the required parameter type indicated on top of the parameter.

Select Callback

A bit of cleanup needs to be performed before proceeding: A semicolon needs to be added at the end of the (now completed) statement and the When class containing the callback definitions needs to be added to the imports.

Clearing Syntax Errors

The method thenDo(..) will be called ‘back’ upon successful creation of the new realm on the onedb cloud. If the parameter name for the thenDo(..) method are something as informative as arg0 .., we can rename the parameter with a more descriptive name such as result or r:

@Override
public void thenDo(WithRealmCreatedResult r) {

The result object will carry the following values:

r.client(): The createRealm operation will create a new client session, which can be accessed through the client parameter. This session will also have the realmRoot and its children available for further operations.

r.root(): The realmRoot parameter points to the root node of the newly create realm; to this node, new nodes to be stored as part of the realm can be appended.

r.secret(): The secret parameter holds a String, which must be supplied when the realmRoot is accessed using the onedb Java Client or the REST API (It’s a kind of access token).

r.partnerSecret(): This parameter will hold no value after the conducted invocation of the createRealm operation. This parameter will hold a secret, which will allow to write (but not read) a node for a postbox type realm.

While the application as given above will successfully create a realm, we will not be able to access this realm after the application is closed, since we will neither know the address of the realm nor the secret token to access it.

To save the realm’s address and access secret, we can, for now, print them to the console by adding a few print statements as follows:

@Override
public void thenDo(WithRealmCreatedResult r) {
    System.out.println("realmRoot: "+r.root());
    System.out.println("secret: "+r.secret());
}

Running the application should result in an output such as the following:

realmRoot: One.reference("https://u1.linnk.it/bkesvc/explora")
secret: maqi______z94

You will notice that applications starts, then prints the output above after a few seconds, but will not stop. In order to terminate our application correctly, we have to shut down every client session we have created. In our case, this is the one client session created by the createRealm operation.

To shut down the client session, we can add the following to the existing application after the last System.out statement:

...
System.out.println("secret: "+r.secret());

One.shutdown(r.client()).and(new When.Shutdown() {

    @Override
    public void thenDo() {
        System.out.println("Session is shut down.");
    }

});
...

When we start the application again, it should now terminate as expected after printing the access information for the realm.

realmRoot: One.reference("https://u1.linnk.it/bgbpce/explora")
secret: gv3______0nx
Session is shut down.

Save both the URL of the node (“…/explora”) and the printed secret for the next steps of the tutorial in a text or source file. You can also rerun the application at any time to obtain this information again.

Note that although we have not changed any parameters of the createRealm operation, the second invocation resulted in a different realm root and a different secret being reported.

[full source code of example on github]

2. Interlude: REST access using web browser

We can access the newly created realm(s) using any web browser using the REST API of onedb: just type in the URI of the realm root reported by your application (e.g. https://u1.linnk.it/bgbpce/explora) into the browsers address bar and hit enter.

If you are greeted by the following friendly message, just select [Proceed anyway] or the available equivalent of your browser.

Add SSL Exception

Supply as authentication the username ‘token’ along with the secret access token reported by your application (e.g. gv3etqoingxe0nx) and press login.

Http Basic authentication

Select save password if you are given the option. You should see the sparse contents of your node displayed by the web browser.

Node rendered as html

You can see different representations of your node by appending a variant (.node. or .value.) and a data format (.html, .xml or .json) to the URI of your node such as:

https://u1.linnk.it/bgbpce/explora.value.xml or
https://u1.linnk.it/bgbpce/explora.node.json

The variant value with the format XML, for instance, should be rendered as follows:

XML Value representation

3. Load

While it is convenient to access nodes using the provide REST interface, it is far easier to access the created nodes using the onedb API. As long as we know the access token (secret) and address of a node, we can access it from any system linked to the onedb library.

For the purposes of this tutorial, we can create another class in our Java application to emulate another app accessing the node. Add a class to your project ‘Load’ and add a main method to this class.

New Class Load

Although both ExploreOneDb and Load are part of the same eclipse project, they form two distinct applications for Java, since both classes have independent main methods. The onedb engine must be initialized once for every Java application. Since Load defines a new application, we need to initialize the onedb engine as follows:

OneJre.init("[your API key here]");

Next we type One. once again but this time chose the operation load(Object node):

Select Load Operation

The load operation now requires the specification of an Object node. This object specifies what is to be loaded. The only information we have available from the invocation of the ExploreOnedb example app is the address of the root node of the realm (e.g. https://u1.linnk.it/bgbpce/explora) plus its access secret you have saved (if not, just rerun the ExploreOnedb application).

onedb distinguishes between resolved nodes with a value and references to nodes. Node references have a unique identity but have no value. We can use such a reference to specify, which node we want to load, for instance:

One.reference("https://u1.linnk.it/bgbpce/explora");
                        // ^-- replace with your URI

Replace the address in above statement with the address you have saved after running the ExploreOnedb example and provide the reference definition as the node to be loaded for the load operation. Again type a . after the closing bracket to see the further parameters for the load operation. Select the parameter .withSecret(String secret):

Parameters for Load Operation

Supply the access token secret you saved earlier (e.g. “gv3__0nx”). Finally, you should define the callback by selecting the option .and(...):

Specifying Callback Parameter

Proceed in the same way as specifying the callback for the createRealm(..) operation, but this time chose the callback new When.Loaded(..). Your application should now look as follows:

OneJre.init("[your API key here]");

One.load(One.reference("https://u1.linnk.it/bgbpce/explora"))
                               // ^-- replace
   .withSecret("gv3______nx")
   .and(new When.Loaded() {

        @Override
        public void thenDo(WithLoadResult<Object> lr) {

        }
});

The callback method (thenDo(..)) returns a load result with a reference to the node, we have just loaded (lr.loadedNode()). The load result also returns a client session (lr.client()). We can retrieve the resolved loaded node as follows:

@Override
public void thenDo(WithLoadResult<Object> lr) {
    Object realmRoot = One.dereference(lr.loadedNode()).in(
            lr.client());

    System.out.println("Node reference: " + lr.loadedNode());
    System.out.println("Resolved node: " + realmRoot);
}

For an in-depth discussion of the various node types and the One.dereference(..) operation please check the article “A Practical Guide on Node Types in onedb“.

Run the application and you should get an output such as the following:

Node reference: One.reference("https://u1.linnk.it/bgbpce/explora")
Resolved node: Nx.define(exploration).at(https://u1.linnk.it/bgbpce/explora)

[full source code of example on github]

4. Append

Thus far, this tutorial walked through the steps of setting up a Java project, creating a realm and accessing this realm using a REST interface and the onedb API. Although creating a realm inevitably results in the creation of one node (the realm root), we have not really done a lot of work with nodes, for instance establishing connections. In this section, I will explain the most important operation to define nodes and connections between them: append.

First create a new class NodeOperations and add a main method, which creates a new realm (check the creating a realm section above for details regarding the following code snippet):

public static void main(String[] args) {
    OneJre.init("[Your API Key]");

    One.createRealm("ops").and(new When.RealmCreated() {

        @Override
        public void thenDo(WithRealmCreatedResult r) {

        }
    });
}

Let’s assume the following scenario: We would like to define a customer in the newly created name with the name “Bob” who lives at “26 Short Av”. To express this information using a proven and tested object-oriented approach should not be too difficult: First, we define a class Customer and subsequently create an instance for Bob.

Class definition:

public static class Customer {
    public String name;
    public String address;
}

Instance Creation:

Customer bob = new Customer();
bob.name = "Bob";
bob.address = "26 Short Av";

We need to make one small modification before we can upload the Bob object to the onedb cloud: The customer class will need to implement the Serializable interface, in order for onedb to be able to transport objects of this type safely to the onedb cloud. This is easy enough; just change the class definition to:

public static class Customer implements Serializable {
    public String name;
    public String address;
}

We can now append the bob object to the ‘ops’ realm root node as follows:

@Override
public void thenDo(WithRealmCreatedResult r) {
    Customer bob = new Customer();
    bob.name = "Bob";
    bob.address = "26 Short Av";

    One.append(bob).to(r.root()).in(r.client());

    System.out.println("Created " + r.root() + ":" + r.secret());
    // do shutdown ...
}

[full source code on github]

You can run the application and should receive the login information for the test realm such as Created One.reference("https://u1.linnk.it/crd87h/ops"):hhz______ni. We can use this reference to load both the realm and the associated bob object using the One API as described above under the load operation.

We can also access the newly created realm using a web browser as described in the REST section above.

REST Access to realm

The picture above points to a problem with the approach taken so far: Although the customer has been added to the realm as a SerializedNode (class NodeOperations$Customer), the data associated with this node cannot easily be interpreted through the REST interface. Indeed, also other Java applications would have difficulties in ‘deciphering’ the data of this node without the definition of the class Customer in byte code. This stays in contrast with the ideals of a small data system!

A better approach is to decompose the information a Customer instance expresses into various nodes with ‘standard’ data types (such as String, Integer …). One way to do this could be as follows:

The root node of the realm is designated to represent the bob entity. We append a node with the text “Bob” to this realm root node to indicate the name. We also append another node with the text “26 Short Av” to the realm root. To indicate the ‘type’ of the used nodes, we further append two generic nodes Address and Customer, which do not hold any particular value to “26 Short St” and the realm root respectively.

The described arrangement of nodes can be visualized as follows:

Example Scenario

We can create a new class NodeOperationsBetter and again specify the logic for creating a new realm. The following code snippet shows how to define a node arrangement as described above using the onedb API.

OneJre.init("[Your API Key here]");

One.createRealm("ops").and(new When.RealmCreated() {

    @Override
    public void thenDo(WithRealmCreatedResult r) {
        OneClient client = r.client();
        Object bob = r.root();

        One.append("Bob").to(bob).in(client);

        String addressValue = "26 Short Av";
        One.append(addressValue).to(bob).in(client);
        One.append("an Address").to(addressValue).in(client);

        One.append("a Customer").to(bob).in(client);

        System.out.println("Created " + r.root() + ":" + r.secret());
        // shutdown client ...
    }
});

[full source code on github]

If we access the realm created with the logic above using the REST interface, the data should be presented in a more accessible manner such as below:

Screenshot: Bob Node

The REST interface allows navigating from one node to another. If we click on ’26 Short Av’, the node representing the address value will be displayed:

Screenshot: Street Value Node

Apart from presenting the information associated with the customer in a more accessible manner, following a connection-oriented approach makes the data semantically richer. For instance, we will have implicitly created a globally accessible type ‘address’ (linked to 26 Short Av Node). The type URI for address will look something like the following:

https://u1.linnk.it/l8hpud/ops/26_Short_A2/an_Address0

However, this URI does not appear to be very ‘pretty’ and portable. In specific, the part ’26ShortA2′ does appear to be in conflict with the intention to define a reusable identity for the type ‘address’.

It is usually a good practice to define ‘type’ nodes in their own independent realm. This way their reusability can be increased and also shorter and more succinct URIs can be created. Type nodes can be appended to a realm like any other node. We can write a little application such as the following to define the type nodes required for the example:

One.createRealm("types").and(new When.RealmCreated() {

    @Override
    public void thenDo(WithRealmCreatedResult r) {
        Object typesRoot = r.root();

        Object addressType = One.append("an Address").to(typesRoot)
                .atAddress("./address").in(r.client());

        Object customerType = One.append("a Customer").to(typesRoot)
                .atAddress("./customer").in(r.client());

        System.out.println("Address type: " + addressType);
        System.out.println("Customer type: " + customerType);
        System.out.println("Types realm " + r.root() + ":" + r.secret());
        // shutdown ...
    }
});

[full source code on github]

Note here the slightly changed append statements with an added atAddress(..) parameter. Specifying the atAddress parameter allows to specify a precise address to be used for nodes; if atAddress is not specified, onedb will attempt to generate a suitable address.

Running the above application should result in an output such as the following:

Created One.reference("https://u1.linnk.it/zednuw/types"):ip_______ib5
Address type: One.value(an Address).at("https://u1.linnk.it/zednuw/types/address")
Customer type: One.value(a Customer).at("https://u1.linnk.it/zednuw/types/customer")

Save the output of your application in a text file or somewhere else for the next steps.

Using the newly created ‘type’ nodes, we can rewrite the definition of customer bob as follows (please remember to change the type nodes in the examples to the type nodes you have created):

// -- reference types
Object addressType =
  One.reference("https://u1.linnk.it/zednuw/types/address");
                             // ^-- replace with your type!
Object customerType =
  One.reference("https://u1.linnk.it/zednuw/types/customer")
                             // ^-- replace with your type!
// -- build data
OneClient client = r.client();
Object bob = r.root();
One.append(customerType).to(bob).in(client);
One.append("Bob").to(bob).in(client);

String addressValue = "26 Short Av";
One.append(addressValue).to(bob).in(client);
One.append(addressType).to(addressValue).in(client);

[full source code on github]

Now, if we were to define a customer Alice, we could reuse the type nodes for ‘address’ and ‘customer’ used for the definition of Bob.

5. Select

Querying data in onedb is done on a recursive from-node-to-node basis much in the spirit of Linked Data. In order to aid the navigation from node to node, onedb provides three operations to query the children of a node:

selectFrom(node).allChildren(): Will return a list of the references of all children appended to the node.

selectFrom(node).allChildrenFast(): Will return a list of addresses as Strings of all children appended to the node.

selectFrom(node).theChildren().withType(type): Will return a list of references to all children of the node with the specified (Java) type.

selectFrom(node).theChildren().linkingTo(reference): Will return a list of references to all children of the node, which have the specified reference as one of their children. For instance, in the example node arrangement given below, selecting all children from the node persons linking to Customer will return the nodes bob and alice.

Example Arrangement linkingTo

Create a new Java class with main method, initialize the onedb engine, create a realm and define the following nodes for the realm:

One.append("This is a test realm").to(r.root()).in(r.client());
Object bob = One.append("bob").to(r.root()).atAddress("./bob")
        .in(r.client());
One.append(
        One.reference("https://u1.linnk.it/zednuw/types/customer"))
                           // ^-- replace with your type node
        .to(bob).in(r.client());

[full source on github]

Note that you will have to replace the reference to the customer type defined above ("https://u1.linnk.it/zednuw/types/customer") with the customer type you have created in the previous section.

Run the application and you should save the link and access token for the realm you have created (e.g. One.reference("https://u1.linnk.it/4hxdr8/query"):zt________y2).

Then, create another Java class with main method, initialize the onedb engine and load the node you have just created using the link and secret you have saved:

One.load(One.reference("[your query realm]"))
        .withSecret("zta_____1y2")
        .and(new When.Loaded() {

            @Override
            public void thenDo(WithLoadResult<Object> lr) {
...

Note that you will have to replace the reference and supplied secret with the details of the query realm you have created above.

Within the thenDo(..) callback of the load operation, you can retrieve the references to all children of the loaded node:

System.out.println("All Children: "+
    One.selectFrom(lr.loadedNode()).allChildren().in(lr.client()));

You can filter the children of the loaded node by their type:

One.selectFrom(lr.loadedNode())
        .theChildren()
        .ofType(String.class)
        .in(lr.client())
        .and(new When.ChildrenSelected<OneTypedReference<String>>() {

            @Override
            public void thenDo(
                    WithChildrenSelectedResult<OneTypedReference<String>> sr) {
                System.out.println("Found Messages:");
                for (OneTypedReference<String> node : sr.children()) {
                    System.out.println("  "
                            + One.dereference(node).in(
                                    sr.client()));
                }
            }

        });

Note that the operation to select children by type in difference to the previous one (select all children) requires the specification of another callback (When.ChildrenSelected). As a general rule, onedb will always require the specification for all operations, which may need to send a remote message to the onedb cloud. The initial loading of the root node of the realm, will download the root node from the onedb cloud including the references to all its children. A reference, however, is not sufficient to determine the (Java) type of a node. Therefore, the select operation with type parameter will need to assure all child nodes have been downloaded from the onedb cloud.

The same applies for specifying the linkingTo(..) parameter. Since the load operation initially only loads the children of a node but not its children’s children, a remote request must possibly be sent to the onedb cloud, requiring the specification of a callback:

One.selectFrom(lr.loadedNode())
        .theChildren()
        .linkingTo(
                One.reference("https://u1.linnk.it/zednuw/types/customer"))
                                 // ^-- replace with your customer type
        .in(lr.client())
        .and(new When.ChildrenSelected<OneTypedReference<Object>>() {

            @Override
            public void thenDo(
                    WithChildrenSelectedResult<OneTypedReference<Object>> sr) {
                System.out.println("Found Customers:");

                for (OneTypedReference<Object> node : sr.children()) {
                    System.out.println("  "
                            + One.dereference(node).in(
                                    sr.client()));
                }
            }

        });

[full source code on github]

Running your application should result in an output such as shown below.

All Children: [One.reference("https://u1.linnk.it/4hxdr8/query/NxAuth.rea0"), One.reference("https://u1.linnk.it/4hxdr8/query/This_is_a_1"), One.reference("https://u1.linnk.it/4hxdr8/query/bob")]
Found Messages:
  This is a test realm
Found Customers:
  One.value(bob).at("https://u1.linnk.it/4hxdr8/query/bob")
All queries completed.

The select example makes heavy use of references (One.reference(...)) and value nodes (One.value(..).at(..). For an in-depth discussion of these different node types and how one node type can be converted into another, please check the article ‘A Practical Guide on Node Types in onedb‘.

6. Replace

Apart from creating realms and appending nodes in complex and deep arrangements, onedb supports to replace the value of nodes as well as removing nodes. However, it is often a good idea to avoid these operations whenever possible. onedb in its core is designed to enable distributed systems: any piece of data or node might be opened by multiple clients on the same or different devices.

The operations remove and replace add one significant factor of uncertainty: mutability. While immutability is a nice property of any software system, it is crucial to the success of distributed systems.

As long as we constrain ourselves to using the operations of createRealm, append and select, the data stored in onedb will be immutable. For instance, when we append a node representing the type customer to a ‘types’ node, it can be guaranteed that this node will be available to any client working with the system.

The operation remove, in particular, can lead to unexpected and undesired situations in a system with many involved clients. However, there are cases, where using update and remove is just by far the simplest solution. To support these cases, onedb offers operations both to update and remove nodes from the network.

The update operation will replace a node value with another node value. The connections of a node remain unaffected by the update operation. The value of nodes with externally managed address can easily be updated as shown in the following:

String phase1 = "phase1";
One.append(phase1).to(realmRoot).in(client);

Object phase2 = "phase2";
One.replace(phase1).with(phase2).in(client);

The example above will first add a node “phase1” to the onedb cloud and then replace this node with a node with the value “phase2”. Please note that although the value of the node has been changed, the address of the node will stay the same:

Before update:

Value  : "phase1"
Address: https://u1.linnk.it/di14a2/update/phase11

After update:

Value  : "phase2"
Address: https://u1.linnk.it/di14a2/update/phase11

In general, it is not allowed to change the address of a node using the update operation. This can become tricky when working with nodes with internally managed addresses. For instance, the following operations present a BAD practice:

OneNode phase1Node = One.append("phase1").to(realmRoot)
        .atAddress("./p1").in(client);

One.replace(phase1Node)
        .with("phase2"))) // WRONG !!!
        .in(client);

The above example would replace a node "phase1" WITH a specified address "./p1" with a node value "phase2" WITHOUT a specified address.

The correct way to update a node with an internally managed address would be as follows:

OneNode phase1Node = One.append("phase1").to(realmRoot)
        .atAddress("./p1").in(client);

One.replace(phase1Node)
        .with(One.newNode("phase2").at(phase1Node.getId()))
        .in(client);

[full source on github]

onedb stores a version every time a node is changed (e.g. replaced, a child is appended, a child is removed ..). Therefore, calling One.clearVersions(...) for nodes, which are frequently changed, can significantly increase the performance of loading and manipulating data. You can find an example on github on how to use the clear versions operation.

7. Remove

As already mentioned in the previous section, replace and remove are operations, which should be used with care in environments, where one node is synchronized between multiple clients. Remove, in this regard, is more ‘dangerous’ than replace, since replace guarantees that once defined nodes are available to other parts of a distributed system. Remove, in contrast, can render nodes unavailable to other components of a system. Therefore, remove should always be used with caution when working with the onedb cloud!

The remove operation can basically be used for two purposes: First, removing one node from another node will delete the connection between these nodes (if a connection has been defined before). However, remove can also be used to remove a node from the onedb cloud; a node will be removed from the onedb cloud, if the connection to its direct parent is removed.

The following snippet will first append a node "to be removed" to the realm root and define it in the onedb cloud. Then, this node will be removed from the realm root AND the onedb cloud.

    // remove connection AND node
    OneNode toBeRemovedNode = One.append("to be removed")
            .to(realmRoot).atAddress("./toBeRemoved").in(client);
    One.remove(One.reference(toBeRemovedNode)).fromNode(realmRoot)
            .in(client);

If, however, a connection between nodes, which are in no direct parent-child relationship is removed, ONLY the connection will be removed and not the connected node. In the following example, the node "to be kept" will still be defined in the onedb cloud even after it has been removed from the node "another node".

    // remove ONLY connection
    OneNode toBeKeptNode = One.append("to be kept").to(realmRoot)
            .atAddress("./toBeKept").in(client);

    OneNode anotherNode = One.append("another node").to(realmRoot)
            .atAddress("./anotherNode").in(client);

    One.append(toBeKeptNode).to(anotherNode).in(client);
    One.remove(toBeKeptNode).fromNode(anotherNode).in(client);

[full source on github]