Vous êtes sur la page 1sur 39

Table of contents

Dolphin - where Enterprise Java meets Desktop Java

Dolphin - Reference Documentation


Authors: The Dolphin team
Version: PRE-1.0

1
Table of Contents
1 Introduction
1.1 Latest Changes
1.2 Credits
2 Architecture
2.1 The OpenDolphin structure
2.2 The concept of presentation models
2.3 The purpose of attributes
2.4 Stable bindings
2.5 Collections of presentation models and the model store
2.6 Understanding the client-server split and threading model
2.7 Communicating via Commands
2.8 Relying on the command sequence
2.9 Discussion
3 How to get started with OpenDolphin
3.1 Adding nodes to a stage, registering an onAction handler
3.2 Introducing a presentation model with one attribute and bind the value
3.3 Logical separation between client and server
3.4 Bind the "dirty" of presentation models to the view
3.5 Split into modules/projects
3.6 Enhanced view, let the "director" wire all application actions
3.7 Remote setup
4 Use Cases and Demos
4.1 The general approach of OpenDolphin with a Login dialog
4.2 Immediate and deferred binding
4.3 The container yard monitoring application
5 tbd Configuration and Setup
5.1 Standalone in-memory usage
5.2 Remote setup
6 tbd Developer Zone
6.1 How to build

2
1 Introduction
Canoo initiated the Dolphin project since we had recognized that many of our customers
share a common quest:
Keeping the application logic on the server but presenting it with all the capabilities of the
client device.

Dolphin is a remoting solution that bridges the world of Enterprise Java and Desktop Java.
Unlike most REST approaches, it doesn't confine the server to be a data source only.

Instead, with Dolphin your server-side business logic controls a shared presentation model.
The client displays the Dolphin state in all its beauty.

The figure below shows how client and server connect via Dolphin's shared presentation
model followed by some introductory slides on the topic.

In terms of Model-View-Controller (MVC) one can see in the figure above that Dolphin puts
the View responsibility on the client but leaves the controlling logic on the server. The model
(here in the sense of a presentation model, not a domain model) is shared.

This is shown in even more detail below.

3
4
Throughout this documentation, we will refer to this introduction and especially the section on
architecture expands on the value of clean structures.

1.1 Latest Changes


Release 0.8

25 issues have been addressed in this release where the most important ones come from our
users that already have OpenDolphin-based applications in production. This ranges from
performance-oriented topics like the support for client-side models, command batching and
compressing down to operational support to e.g. detect unwanted breaches of the session
affinity.

The full list of closed issues is at the project JIRA

Release 0.7

There are no changes in this release. It has only been built to set the maven group id to
org.open-dolphin to comply with the conventions on maven central.
The project artefacts are now available for download from MavenCentral

Please refer to DolphinJumpStart for an example on how to use OpenDolphin with either
Maven or Gradle.

Release 0.6 : Notable Changes since 0.5

The 0.6 release has only one change, which is a rather big and breaking one:

The package com.canoo.dolphin has been renamed to org.opendolphin.

5
See also DOL-34

Release 0.5 : Notable Changes since 0.4

The project artefacts are now available for download from MavenCentral

Release 0.5 introduced support for asynchronous server-side events, which are distributed
through an event bus. Typical use cases for this event bus are:
instant visualization of server side events (think JMS or hibernate events)
consistent visualization of transient data across many clients
cross-client notifications

Along with this capability comes a series of demos, which live directly in the OpenDolphin
code base. Some of these demos also have a video presentation on YouTube:
ManyEventsDemo where 10'000 alarm events happen on the server and 6 clients update
instantly and consistently
TrainControlDemo where a dash board changes the train speed and other clients are
notified
LazyLoadingDemo with 100'000 rows in a table
PortfolioDemo where the management of a portfolio serves as a typical example of a
business application

Version 0.5 upgraded dependencies to Groovy 2.1.1. and GPars 1.0. We make use of the
new @CompileStatic where appropriate and enhanced execution speed by 60%.

Release Notes - Dolphin - Version 0.5

Release 0.4 : Notable Changes since 0.3

6
1. DOL-25 - provide a demo that measures dolphin response times
2. DOL-33 - allow more meta-infos per Attribute
3. DOL-19 - Provide a code example that shows how to use dolphin for lazy loading
4. DOL-21 - Build does not automatically discover javafx in JDK when JAVAFX_HOME is
set
5. DOL-37 - align server and client side API for CRUD operations on presentation models
and attributes
6. DOL-38 - nicer API for server side value change
7. DOL-39 - API enhancement: server actions should work transparently on the response
8. DOL-40 - API enhancement: allow finding presentation models and attributes without the
need for casting in both Java and Groovy friendly manner
9. DOL-41 - start a user guide
10. DOL-42 - provide combined apidoc: javadoc / groovydoc
11. DOL-43 - Provide a DataCommand
12. DOL-44 - Provide a more complex demo (dependencies, crud operations, remoting)
13. DOL-17 - provide a 'gradlew run' to start any demo
14. DOL-18 - link the website to the latest version of the documentation
15. DOL-32 - remove the "linking" feature

Release Notes - Dolphin - Version 0.4

Breaking Changes
The "linking" feature is no longer available. See e.g. the Crud demo for alternatives.

Deprecations
In the future, we will disallow direct access to the connector or model store.

Please prepare by using the dolphin (ClientDolphin and ServerDolphin) facade


whenever possible. This also true for all the cases where application code on the server side
directly invokes constructors of commands, presentation models or attributes. Please use
factory methods instead.

Solved Bugs

1. DOL-15 - Basic build fails


2. DOL-22 - exception in reference table demo and search demo
3. DOL-26 - Binding the text property of a Swing component to a ClientPresentationModel
throws Exception
4. DOL-31 - ServerDolphin.createPresentationModel has an "optional" parameter, which is
required
5. DOL-35 - com.canoo.dolphin.demo.startCreatePresentationModelDemo.groovy still tries
to invoke serverDolphin.createPresentationModel

7
1.2 Credits
A big thank you goes to all our early-adopting users that have provided us with much
appreciated feedback, inspiration, and other support, particularly
Oracle
Navis
Knappschaft Bahn-See
Siemens
Inventage
and other companies that prefer to remain unnamed.

8
2 Architecture
This user guide first explains the general approach of OpenDolphin and its architecture before
going into details and examples.

Reading through the user guide in this sequence is for those who like to first understand
things on a general level.

Others prefer to start with concrete demos and use cases, which is just as legitimate. In this
case, you are welcome to skip the architecture section and directly jump to the demos.

The Dolphin architecture is made from two parts:


1. the structure
2. concepts and rules

2.1 The OpenDolphin structure


Modules define the vertical structure

Dolphin consists of three parts:


1. the shared module
2. the client module
3. the server module

Both, client and server depend on the shared module.

There is an additional fourth module called "combined", which depends on all three modules
above and allows combining them in one single Java virtual machine for automatic testing,
debugging, profiling and demo purposes.

All modules live in their own subproject with a separate source tree and the Gradle build
system is used to build them according to their dependency structure.

Each project that uses Dolphin will most likely resemble this structure where the shared
module may be empty (in case they is no shared knowledge between client and server).

The Dolphin team has produced a JumpStartProject that suggests a best-practice project
setup for Dolphin-enabled applications, either with
a Maven build or
a Gradle build.

Dolphin itself comes with a substantial list of demos in the subproject "demo-javafx". It is
highly recommended to have a look at the demos in order to understand how we expect
Dolphin to be used.

The horizontal layering

9
Orthogonally to the vertical structure of modules, there is a simple horizontal layering that
spans across all modules. In consists of:
the common infrastructure for build automation, configurations, etc.
the communication layer that provides the basic implementation
the facade layer that provides the API
the demo layer that shows all Dolphin features in small examples

The layering is visible through the respective package structure.

The application programmer should only use the facade layer.

The figure below summarizes the vertical and horizontal structuring.

While the structural part of the Dolphin architecture is rather obvious and straight-forward, the
concepts and rules that stand behind its inception require a bit of explanation, which is the
topic of the next section.

2.2 The concept of presentation models


Presentation models are probably best known because of the presentation model design
pattern by Martin Fowler.

They also come under different names like Application Model or View Model .

There is a lot of information about presentation models on the web, e.g.

10
from Microsoft Developer Network
from Canoo
from JGoodies

Dolphin doesn't claim to have "the best" implementation of this pattern in any sense of the
word.

In fact, we do not even claim to implement the "presentation model design


pattern" at all!

We would like to avoid any discussion about whether we meet all


requirements of any pattern or structure as proposed by any authority. We
are happy to give them all due credit, though, since we are building on their
work.

It just happens that the Dolphin approach has a lot of similarities with the patterns above,
which led us to calling our abstraction a presentation model.

What is common
Dolphin presentation models capture what should be visualized (as opposed to how).
They are different from domain models.
They allow multiple consistent views on the same information.
They know neither views nor controllers (only their listeners).
They are independent of any GUI toolkit or widget set.
They make replacing the GUI toolkit easier.
They decouple business logic from view logic.
They make UI code easily testable.

They consist of Attributes (like many but not all other implementations).

They support declarative binding between attributes and GUI elements.

What is special

11
Dolphin presentation models are generic . You never implement a new class for a new
type of presentation model.
They do not contain any custom behavior (only what's provided by Dolphin for binding
and remoting).
They allow stable bindings.
They are flat. Their attributes contain no object references, only primitive values. There is
no object graph.
In particular, they do not maintain an object reference to a domain model (but maybe a
key).
They are shared between a client and a server. Dolphin cares for consistent
synchronization.
They live in a managed object space. All instances are known and can be retrieved by
their characteristics.
They have a unique id , which either the application programmer provides or is
automatically assigned otherwise.
They optionally have a type parameter for logical grouping.

See also: Usage and API doc.

Where the logic goes

When there is the least bit of business logic, it goes into an action. Actions live in controller
classes of the server module.

There is a small fraction of logic that resides on the client, which is view logic like e.g. for
calculating component bounds when layouting.

2.3 The purpose of attributes


Each presentation model can refer to many attributes.

Attributes maintain no back-reference. Technically, they can be shared between presentation


models.

Attributes are generic. You never need implement your own attribute type.

Each attribute has a unique id and a propertyName that is unique per presentation
model.

Attributes encapsulate a primitive value and allow listeners to be notified about value
changes. Technically, this is done via the standard Java PropertyChangeListener
type. In other words, attributes are Observable.

In addition to the value, attributes provide more information about the value, e.g. whether the
value is dirty (i.e. it differs from the base value ). The dirty state is observable such that you
can bind against it. As soon as at least one attribute of a presentation model is dirty, that
presentation model is also considered dirty.

12
Attributes have an optional qualifier property to capture the fact that this attribute represents a
qualified feature in the domain world. A qualifier like "person.4711.firstName" may be used to
explain that this attribute represents the value of the firstName property of Person
domain class with id 4711.

Whenever the value of an Attribute changes, all other attributes with the same qualifier are
automatically updated.

Qualifiers are a prerequisite for stable bindings.

Since version 0.4 Dolphin attributes can be tagged in order to describe the meaning of the
value. The Tag enumeration provides all possible tags with VALUE being the default. Tags
can be used to e.g. describe whether the person firstname is a MANDATORY attribute,
whether it is ENABLED, VISIBLE, and so on. Attributes with a tag are observable in the
usual way.

See also Usage and API doc.

2.4 Stable bindings


Binding is just another word for an event listener listening to an event source.

OpenDolphin supports binding between attributes and arbitrary other objects where most of
the time, these other objects are views - with the notable exception of the dolphin
infrastructure itself, which automatically listens for value changes in order to notify the server.

While OpenDolphin is independent on any specific UI toolkit and works out-of-the-box with
every technology that understands PropertyChangeListeners (e.g. SWT and Eclipse RCP), it
provides special binding facilities for
JavaFx and
Swing.

Bindings can work in both directions such that a view automatically reflects any change in an
attribute value or such that an attribute value is automatically updated with every user input to
a UI component.

An important design goal of OpenDolphin is to keep bindings stable.

For the typical use cases it should not be necessary to "unbind" or "rebind", not even for use
cases where a selection changes the to-be-displayed information, commonly known as
master-detail views.

Master-detail views come in many flavors:


a list or table row selection with an associated detail view (often forms) for the row info
a map or chart item selection with details per selected item
a tabbed pane with the individual details (often forms) per tab

OpenDolphin allows to bind the detail view against a stable placeholder presentation model
that is automatically synchronized (both read and write) with the selection.

13
The detail view never has to change its binding!

In fact, this feature goes beyond master-detail views and applies to all referential structures,
which otherwise are a very difficult to handle consistently in a user interface.

See also Usage.

2.5 Collections of presentation models and the model store


Any reasonably sized UI will have not only single presentation models but many of the same
type.

Let's assume that the application displays a list of vehicles where each vehicle is a
presentation model instance of type "vehicle".

The list view must add a new row whenever a new vehicle becomes available and must
remove the row when the vehicle becomes unavailable.

To this end, the list view can register itself as a ModelStoreListener and will be
notified whenever a presentation model is added to or removed from the store.

The model store defines the managed object space for presentation models.

There are two such stores: one on the client and one on the server. Both are automatically
synchronized by OpenDolphin.

Conceptually, one can see the model store as


a distributed, in-memory, no-sql database with only two tables (presentation model and
attribute)
a specialized event bus (event provider is only the store itself and only ModelStoreEvents
are issued)

The application programmer should not access the model store directly but only through the
facade layer, see Usage.

The model store is used internally as a value change listener to all known attributes in order to
consistently update all attributes of the same qualifier
notify the server about changes

Now, let's have a look how the client-server split is designed in OpenDolphin.

2.6 Understanding the client-server split and threading model


A dolphin application has a client and a server module. As we will see in remote config and
standalone config this does not necessarily mean that client and server live in different VMs,
but they can.

With OpenDolphin, the client is responsible to visualize presentation models, the server is
responsible to manage the presentation model data.

In order to keep client and server model store consistent, one must be chosen as the master
and the dolphin team has chosen the client to be that master.

14
All changes first happen on the client. Then the server is notified, and he updates
automatically.

Any server-side changes happen by the server sending a command to the client instructing
him which change to apply.

All communication between client and server happens asynchronously no matter whether an
in-memory or remote configuration is used.

Despite the asynchronous communication, OpenDolphin guarantees notification delivery in


the same sequence as they originated.

OpenDolphin assumes the client to have an event model and the server to live in a
request-response model. OpenDolphin bridges these two worlds.

Despite the asynchronous communication between client (view) and server (controller), all
processing that takes place as a result of this communication is automatically executed inside
the client's UI thread. Especially all changes to the client model store, client presentation
models, and attributes are subject to this thread-confined approach. Thus, all binding events
will be fired in the UI thread and the views are automatically updated correctly.
With the clear structure and division of responsibilities that OpenDolphin imposes onto the
application a lot of otherwise common headaches around proper thread handling disappear.

2.7 Communicating via Commands


All communication in OpenDolphin happens with the help of the Command Pattern.
Notifications from the client to the server are sent as commands.
Instructions from the server to the client are sent as commands.
The client can request additional data (beyond presentation models) with the help of a
DataCommand.

Commands are simple POJOs and there is a limited set of commands available for
synchronization between client and server.

On the application level only the following commands are used:


NamedCommand is a generic command to trigger a server side action that was
registered for this name
DataCommand requests arbitrary data that is unrelated to presentation models

There is no need to implement any application specific command classes.

In remote scenarios, commands are encoded and decoded via a codec (e.g. JSONCoDec).

A transport (e.g. HttpTransport or InMemoryTransport) can be configured to use such a codec


.

Commands are used as a unit of transport since they are simple to encode/decode but also
since they can be optimized in a number ways (buffering when the server is temporarily
unavailable, batching, elimination of duplicates) and they open the opportunity for later
inclusion of undo/redo capabilities.

15
Since commands are sent asynchronously, one cannot wait for a command to complete.
When sending a command, though, one can provide an OnFinishedHandler that is
called back as soon as the command has returned. The list of all presentation models that
were affected by that command is passed as argument into the callback methods.

See also Usage.

2.8 Relying on the command sequence


We have seen that OpenDolphin uses commands for the communication between views and
controllers, that commands are sent in strict sequence even though they are executed
asynchronously, and that commands may have an onFinishedHandler attached to it.

It goes without saying that any onFinished handler is only executed after the controller action
is finished. Therefore the name.

But despite the sequence guarantees that OpenDolphin gives you, there are a few things to
consider about the asynchronous programming model where it is inherently different from
procedural, UI-blocking code.

Let us first have a look at a typical chain of events.

The sequence of independent commands

The figure below depicts the chain of events that happens when three independent
commands A, B, and C are sent in immediate sequence. Time goes from top to bottom and
the three columns represent three concurrent processing units: the command queue, the
controller actions (can even be remote), and the onFinished activity after the action returned.

1) A, B, and C sit in the command queue (column 1).

2) A is removed from the queue and its actions are processed outside the UI thread, maybe
even remotely on the server (column 2). B and C remain in the queue (column 1).

3) All A actions are finished and the onFinished handler is called (column 3). While
A.onFinished is executed inside the UI thread B was removed from the queue and the B
actions are processed concurrently outside the UI thread (column 2).

16
The execution of A.onFinished often triggers further commands (A1 and A2) that are fed into
the queue (column 1) just like any other ordinary command.

At this point the B actions are processed without the effects of A being
visible.
B must not depend on the effects of A!

4, 5, 6, 7) Emptying out the command queue.

Let's summarize the behavior so far


commands are always processed in the strict sequence in which they appear in the
command queue
the onFinished handler is always executed after the respective action is finished
a command is only processed after the preceding one has finished its actions (but not
necessarily its onFinished handlers)
an onFinished handler is only executed after the preceding onFinished handler is finished

Wouldn't it all be simpler if we waited for A to completely finish including all actions that it may
possibly spawn before processing B like in procedural programming? No. This would mean
that we have to block the queue during that time, which in turn would block the UI - and
blocking the UI is the worst you can do.

" But what if B depends on the effects of A ?" This is when we need the onFinishedHandler as
explained in the next section.

When commands depend on previous ones

Let's assume that A creates a presentation model and B changes a value in that model. Then
B depends on A and we have to make sure that at the time when the B action is processed all
effects of A are visible, e.g. the presentation model is available in the model store.

This is when we place the sending of the B command in the onFinished handler of the A
command. So instead of

clientDolphin.send "A"
clientDolphin.send "B"
clientDolphin.send "C"

we do

clientDolphin.send "A", {
println "Hey, we are inside the onFinished handler!"
clientDolphin.send "B"
}
clientDolphin.send "C"

17
The figure below shows the resulting command sequence:

Note row number 3. Only in the course of A.onFinished being executed (column 3) is the B
command added to the command queue (column 1) and guaranteed after A1 and A2, which
notify the server of the created presentation model. So all relevant state is properly updated
when B's actions (6) and onFinished handler (7) are called.

Practical considerations

Asynchronous programming models all have in common that when some logic is dependent
on some asynchronous task there is a callback like the onFinished handler involved.

This leads to the question of what to do with a chain of dependencies. Does that automatically
lead to deeply nested callback structures that are difficult to write and understand? Not
necessarily.
Various solution are on the market (e.g. "promises") but OpenDolphin has a simple way
where we do not need to understand another concept. We simply send an extra command.

This command doesn't need to do anything, only provide us with an onFinished handler such
that we can add us to the command queue at the appropriate time when all is ready. You
guessed it: such a command already exits. It is the EmptyCommand and you can send it via
the ClientDolphin's sync method.

So even if you have a dependency chain like A <- B <- C there is no need to write

clientDolphin.send "A", {
clientDolphin.send "B", {
clientDolphin.send "C"
}
}

but you can issue the command in this context-free fashion:

clientDolphin.send "A"
clientDolphin.sync { clientDolphin.send "B" }
clientDolphin.sync { clientDolphin.send "C" }

18
Likewise, when B and C depend on A but not on each other (A <- B, A <- C) you can code this
as

clientDolphin.send "A"
clientDolphin.sync {
clientDolphin.send "B"
clientDolphin.send "C"
}

The above has the additional effect that when B has finished, C will immediately follow without
any other command possibly sneeking in between the two, no matter whether it originates
from user input or preceding commands. In that sense you can see the sync as enclosing
an atomic operation.

See also Usage.

2.9 Discussion
No architecture documentation is complete without explaining the rationale behind the
decisions - which alternatives were considered and why other approaches were not chosen.
To that end, we would like to go through a number of questions that we had to answer when
designing OpenDolphin and we will do so in the style of a discussion.

Why choosing presentation model as the main pattern?

There would have been other candidates: Model View Presenter, Passive View, Supervising
Controller, Event Bus, and many more.

All such patterns have their benefits and particularly a distributed event system would have
played well with the intended remoting capabilities.

It were mainly the good experiences we made with the presentation model pattern in a
number of large event-based systems that it became our first choice. The distinction between
what and how turned out to be easier to explain than other approaches. It always gave the
team good guidance when developing the system. Also we had the honor to have the
grandmaster of this pattern Dr. Dieter Holz in the team.

Following the pattern was always easy enough even though it required writing the respective
classes all over again with each new project. As soon as we found out how to generalize the
pattern and combine it with stable bindings and reliable remoting, we never looked back.

Why so much attention on stable bindings?

Our first attempt of a canonical implementation of the presentation model pattern (which is still
around under the name GRASP) turned out to become really complex and not reliable
enough to build solid remoting upon because of the intricacies of "rebinding" when switching
references to attributes.

Let's say a text field should bind against the first name of person A. Then you switch to
person B. Now try to think through all the implications that this has in terms of consistently
rebinding all views and other listeners and informing the server. We lost many a night's sleep
over this seemingly simple issue. And it gets worse when you don't have a generic
implementation.

19
As soon as Andres Almiray discovered how to do stable bindings, everything seemed to just
fall in place.

Why generic implementations?

Presentation models, attributes, and commands are all generic.

The alternative would have been to provide superclasses that are to be extended with custom
state and behavior.

With generic implementations, we not only have the benefit of fewer classes in the system but
more importantly less structural duplication.

We also avoid all the versioning problems that inevitably arise with shared application classes
between client and server.

But most of all, with generic implementations it is much easier to control the system. With
subclasses, you never quite know what they are doing in extend and whether e.g. it is now
safe to delete them. Generic implementations are much easier to keep cohesive.

By the same train of thought, generic implementations are easier to build upon. Since you
exactly know what they are and what they do, it is much easier to build convenience methods
in the facade, e.g. to provide new bindings to yet unknown UI toolkits.

Generic commands provide the option to later extend the system to non-Java clients (web
and mobile).

Why not simply using REST?

OpenDolphin is actually free to use a REST transport if so configured (while the current
HttpTransport is actually using only the POST method and therefore doesn't really qualify as
pure REST).

Even though these are not necessarily constituent features of REST, people often understand
REST as
a stateless server
message-passing communication (all required info is transferred with every request)

This has obvious benefits and may be the right choice in many scenarios but it doesn't allow
to have application logic on the server (only data-access logic, possibly wrapped in services).
You end up with a "fat" client that contains the major part of the application logic.

With OpenDolphin the server always knows the exact state of the client and can act
accordingly, sending the least amount of data.

As compared to REST, OpenDolphin users typically have to wait less, because


data sending is done most often without the user noticing (asynchronously)
when the user really has to wait for the server, the package size is as small as possible
since client and server know the same state, they only have to send diffs
small packages (most often only one tcp/ip package) have the lowest latency

20
With the application running on the server, OpenDolphin is in full control what the client
displays - just like with traditional HTML applications (but with richer visualisation capabilities).
When a new server version is deployed, all clients are instantly controlled by the new
behavior.

Running the application on the server can also have a very positive impact on security,
privacy, consistency, and legal issues.

All the benefits of OpenDolphin over pure REST come at the expense of maintaining state on
the server side, having a bigger memory footprint and affecting horizontal scaling with the
need for sticky sessions.

Those who would like to enjoy the binding, presentation model structuring, testing capabilities,
toolkit independence, and all the other benefits of OpenDolphin, but prefer REST (or other)
remoting for data access, can use OpenDolphin with the in-memory configuration.

21
3 How to get started with OpenDolphin
For an easy entry into OpenDolphin, we will follow the steps of the DolphinJumpStart project.

We implement a very simple application that contains only one text field and two buttons to
'save' or 'reset' the value. 'Saving' will do nothing but printing the current field value on the
server side.

Both buttons are only enabled if there is really something to save/reset, i.e. the field value is
dirty. The dirty state is also visualized via a CSS class (background color changes). Resetting
triggers a 'shake' animation.

Steps 0 to 4 solely live in the "combined" module for a simple jumpstart before we properly
split client and server in step 5 and only keep a starter class in "combined".

Step 7 produces a war file that you can deploy on e.g. tomcat and the client starter moves to
the "client" module.

Setup and start of a basic JavaFX view

Let's start with the setup.

Please make sure you have visited the DolphinJumpStart project and have looked at the
readme.

You can either choose to clone the repo for following each step (recommended) or use the
provided zip files for a Maven or Gradle build of your own application.

The root directory contains a pom.xml that you may want to point your IDE to for creating a
project. All major IDEs should understand this.

In case you are not using any IDE, follow the readme for how to build and run the various
steps.

We start our development in the "combined" module with the simplest possible JavaFX view
step0.JumpStart. The class has a main method such that you can start it from inside the IDE.
Otherwise use the command line launcher as described in the readme.

When your setup is correct, it should appear on your screen like

The gist of the code shows a simple call into the JavaFX API.

22
public class JumpStart extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new Pane(), 300, 100));
primaryStage.setTitle("Dolphin Jump Start");
primaryStage.show();
}
public static void main(String[] args) {
launch(JumpStart.class);
}
}

You are free to also use any other Java-based widget toolkit at this point: Swing, AWT, SWT,
Eclipse RCP, and else. That makes no difference to OpenDolphin.

Of course, the application needs some sensible content, which we will add right away - still
without any OpenDolphin specifics.

3.1 Adding nodes to a stage, registering an onAction handler


We stay in the "combined" module and enhance the JavaFX view step1.JumpStart. just
slightly with a text field and a button that prints the content of the textfield when clicked.
The application should appear on your screen like

The code now contains references to the widgets

private TextField field;


private Button button;

and an action handler

button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent actionEvent) {
System.out.println("text field contains: "+field.getText());
}
});

The printing of the field content is our "stand-in" for a real business logic. You can easily
assume some persistence action at this point or "service" calls in general.

Now it is time to introduce OpenDolphin.

23
3.2 Introducing a presentation model with one attribute and bind
the value
In step 2 we refactor the JavaFX application into step2.JumpStart to make use of
OpenDolphin.

The visual appearance and the behavior has not changed

but the code has.

As an intermediate step, we have put the OpenDolphin setup and the usage in the same
place. Don't worry if that looks ugly. We will clean this up in a minute.

Focus on these lines in the code:

We create a presentation model with the distinctive name "input" and an attribute for the "text"
property.

PresentationModel input = clientDolphin.presentationModel("input", new ClientAttribute("text"));

Note that we not define a "JumpStartPresentationModel" or so since presentation models in


OpenDolphin are totally generic.

Behind the scenes (no pun intended) happens quite a lot:


the input presentation model is added to the client model store (with indexes being
updated)
the client dolphin registers itself as a property change listener to the value of the "text"
attribute
the server dolphin is asynchronously notified about the creation, which you can observe
in the logs
the server dolphin asynchronously updates its model store accordingly.

While this happens, we bind the text property of the text field (this is a JavaFX property) to the
"text" attribute of the input presentation model

JFXBinder.bind("text").of(field).to("text").of(input);

24
Note the fluent API for setting up the binding.

The above is plain Java. When you use Groovy, you can make use of
Groovy's command chain syntax that allows writing the exact same code
as
bind "text" of field to "text" of input

Finally, the action handler that was part of the (client) view before now moves to the (server)
controller. We register it as an "action" on the server-dolphin.

config.getServerDolphin().action("PrintText", new NamedCommandHandler() {


public void handleCommand(NamedCommand namedCommand, List<Command> commands) {
Object text = serverDolphin.getAt("input").getAt("text").getValue();
System.out.println("server text field contains: " + text);
}
});

Note that the (client) view and the (server) controller do not share any
objects!

The dolphin server action must therefore ask the server-dolphin for the "text" value of the
"input" presentation model before he can print it.

Triggering the server action becomes the remaining statement in the button's onAction
handler.

button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent actionEvent) {
clientDolphin.send("PrintText");
}
});

When we now start the application we see in the log:

[INFO] C: transmitting Command: CreatePresentationModel pmId input pmType null attributes


[[propertyName:text, id:761947653, qualifier:null, value:null, tag:VALUE]]
[INFO] S: received Command: CreatePresentationModel pmId input pmType null attributes
[[propertyName:text, id:761947653, qualifier:null, value:null, tag:VALUE]]
[INFO] C: transmitting Command: ValueChanged attr:761947653, null ->
[INFO] S: received Command: ValueChanged attr:761947653, null ->
[INFO] C: server responded with 0 command(s): []
[INFO] C: server responded with

telling us that the presentation model has been created and the value changed from null to an
empty String, the JavaFX default value for text fields.

Let's enter "abcd":

25
[INFO] C: transmitting Command: ValueChanged attr:761947653, -> a
[INFO] S: received Command: ValueChanged attr:761947653, -> a
[INFO] C: server responded with 0 command(s): []
[INFO] C: transmitting Command: ValueChanged attr:761947653, a -> ab
[INFO] S: received Command: ValueChanged attr:761947653, a -> ab
[INFO] C: server responded with 0 command(s): []
[INFO] C: transmitting Command: ValueChanged attr:761947653, ab -> abc
[INFO] S: received Command: ValueChanged attr:761947653, ab -> abc
[INFO] C: server responded with 0 command(s): []
[INFO] C: transmitting Command: ValueChanged attr:761947653, abc -> abcd
[INFO] S: received Command: ValueChanged attr:761947653, abc -> abcd
[INFO] C: server responded with 0 command(s): []

Every single change is asynchronously sent to the server dolphin. Note that the user interface
does not block.

Finally, we hit the button

[INFO] C: transmitting Command: PrintText


server text field contains: abcd
[INFO] S: received Command: PrintText
[INFO] C: server responded with 0 command(s): []

Our server action does it's printing action asynchronously, particularly not in the UI thread!
You can see the asynchronous behavior by the line ordering in the log above. If it were
synchronous, lines 2 and 3 would never be in this order.

Note that even though all the logic runs in-memory, we have the first benefits from
OpenDolphin:

All actions are executed asynchronously outside the UI thread.


We cannot accidentally block it by long-running or failed operations, which
is a common error in UI development.

With the first dolphinized application running, let's clean up and add a bit more OpenDolphin
goodness.

3.3 Logical separation between client and server


In step3.JumpStart we first cleanup the code such that it becomes more obvious, which part
belongs to the (client) view and the (server) controller. In the first place, OpenDolphin leads to
a logical view-controller distinction and client-server split. The only thing that is optionally
shared are constants.

It is always a good idea to refactor literal values into constants, especially if they are used in
more than one place for a unique purpose. Therefore, pull out our String literals into static
references:

private static final String MODEL_ID = "modelId";


private static final String MODEL_ATTRIBUTE_ID = "attrId";
private static final String COMMAND_ID = "LogOnServer";

The configuration setup should move into the constructor:

26
public JumpStart() {
config = new DefaultInMemoryConfig();
textAttributeModel = config.getClientDolphin().presentationModel(MODEL_ID, new
ClientAttribute(MODEL_ATTRIBUTE_ID, ""));
config.getClientDolphin().getClientConnector().setUiThreadHandler(new JavaFXUiThreadHandler());
config.registerDefaultActions();
}

This leaves the "start" method with "view" responsibilities only: the initial contruction and
separate method call for binding and registering actions.

@Override
public void start(Stage stage) throws Exception {
Pane root = PaneBuilder.create().children(
VBoxBuilder.create().children(
textField = TextFieldBuilder.create().build(),
button = ButtonBuilder.create().text( "press me").build(),
HBoxBuilder.create().children(
LabelBuilder.create().text( "IsDirty ?").build(),
status = CheckBoxBuilder.create().disable( true).build()
).build()
).build()
).build();
addServerSideAction();
addClientSideAction();
setupBinding();
stage.setScene(new Scene(root, 300, 100));
stage.show();
}

We add an additional labeled checkbox to visualize the status: whether the text field - or
better say the dolphin attribute that stands behind it - is considered "dirty".

As soon as you change the content of the text field, this checkbox should become selected. If
you remove your edits, it should become unselected again!

Here is how the binding for that requirement looks like:

JFXBinder.bind("text").of(textField).to(MODEL_ATTRIBUTE_ID).of(textAttributeModel);
JFXBinder.bindInfo("dirty").of(textAttributeModel.getAt(MODEL_ATTRIBUTE_ID)).to("selected").of(status);

At this point we see the next benefit of presentation model and attribute abstractions: they can
provide more information about themselves and can carry additional state that is automatically
updated and available for binding.

Each attribute has a "base" value. When the current value differs from that base value, it is
considered "dirty". A presentation model is dirty, if and only if any of it's attributes is dirty.

27
With this knowledge, we can even do a little more.

3.4 Bind the "dirty" of presentation models to the view


In step4.JumpStart we make even further use of the bindable dirty state.

First, we are binding not against the dirty state of an attribute, but against the whole
presentation model behind it. This simplifies the binding:

JFXBinder.bindInfo("dirty").of(textAttributeModel).to("selected").of(status);

Second, we also want the button to only be enabled when there is something reasonable to
do, i.e. when there is some value change in the form. This is a very common requirement in
business applications.

Now, JavaFX buttons do not have an "enabled" state, only a "disabled" state with the opposite
logic. Luckily, our binding facilities are perfectly able to handle this with a converter:

JFXBinder.bindInfo("dirty").of(textAttributeModel).to("disabled").of(button, new Closure(null) {


protected Object doCall(boolean dirtyState) {
return !dirtyState;
}
});

The converter is a Closure (coming from Groovy). But no worries. We can perfectly use it in
Java. Future API extensions may offer a more specific converter type here.

You probably guessed that this code looks nicer in Groovy. Yes, it does:

bindInfo "dirty" of textAttributeModel to "disabled" of button, { state -> !state }

Once the code is so nicely broken into independent parts we can put the various parts in
separate modules for better dependency management.

3.5 Split into modules/projects


Step 5 distributes the code into multiple modules (IntelliJ IDEA parlance) or
projects/subprojects (Gradle, Maven, Eclipse parlance). We use the more generic word
"module".

The combined module depends on both client and server and is used for starting the
application with the in-memory configuration. The sole class that lives in this module is the
starter class step5.TutorialStarter . It sets up the configuration, registers the
application-specific actions on the (server) controller, and starts the view. This is the class to
start from inside the IDE.

The client module (or "view" module if you wish) contains the step5.TutorialApplication view.

28
You can see that the view code is pretty much the same as our old application code but
contains the view specific parts only. There is one additional change, though. When the
button has been pressed and the command has been executed on the server we would like to
interpret the current content of the text field as the new base value just as if the error-free
execution of the command would imply a correct "save". The "disabled" state of the button will
reflect the new non-dirty state.

To this end, we make use of an onFinished handler:

public void handle(ActionEvent actionEvent) {


clientDolphin.send(CMD_LOG, new OnFinishedHandlerAdapter() {
@Override
public void onFinished(List<ClientPresentationModel> presentationModels) {
textAttributeModel.getAt(ATT_FIRSTNAME).rebase();
}
});
}

Please note that the onFinished handler will be called asynchronously inside the UI thread. It
may trigger changes in the model store, which may lead to changes in the display and any
such changes must occur in the UI thread.

The server module (or "controller" module if you wish) contains the step5.TutorialAction
controller.

Both, client and server depend on the shared module, which makes the
step5.TutorialConstants known to both parties. The shared module itself does not depend on
anything.

The code that contains the shared constants now also cares for the uniqueness of certain
Strings, particularly of IDs used to retrieve presentation models and named commands.

public static final String PM_PERSON = unique("modelId");


public static final String ATT_FIRSTNAME = "attrId";
public static final String CMD_LOG = unique("LogOnServer");
private static String unique(String key) {
return TutorialConstants.class.getName() + "." + key;
}

Splitting four classes into four different modules may look a bit over-engineered at this point
but it is an indispensible step before we can go into true remoting and before we can instantly
switch between in-memory- and client-server-mode.

If you fear that this is too much work for setting up the directory structure or the build-time
dependencies: simply unzip one of the project templates and you are good to go.

We get the following benefits:

29
ability to start the code unmodified with different configurations (in-memory or
client-server)
clear and minimal dependencies when building
a minimum of shared code (only the constants) to express semantic dependencies as
syntatic dependencies
actions cannot "accidentally" reach out to view code. The widget set is not even on the
classpath!
actions cannot possibly block the UI thread
view changes are always displayed correctly since they happen in the UI thread
the separation of responsibilities is enforced by the dependency structure

3.6 Enhanced view, let the "director" wire all application actions
We finish the application with some more refactorings in step6.TutorialApplication and some
tweaks to the view such that it appears like

The true value of the change is not visible in a screenshot, though, since it is in the behavior.
The modified background color of the text field appears as soon as it becomes dirty and is set
back to the original state when the dirty state is set back.

To make this happen, we enhance the binding with a little trick in the converter that adds the
"dirty" style class to the text field when needed and removes it otherwise.

JFXBinder.bindInfo("dirty").of(textAttributeModel).to("style").of(textField, new Closure(null) {


public String call(Boolean dirty) {
if (dirty) {
textField.getStyleClass().add( "dirty");
} else {
textField.getStyleClass().remove( "dirty");
}
return "";
}
});

The tutorial.css contains the definition of that style, which makes the code very flexible should
we later decide to visualize the dirty state differently.

30
.root {
-fx-background-color: linear-gradient(to bottom, transparent 30%, rgba(0, 0, 0, 0.15) 100%);
}
#content {
-fx-padding : 20;
-fx-spacing : 10;
}
.dirty {
-fx-background-color: papayawhip;
}

Once we have the view code so nicely refactored to be free of any other responsibility, we can
spend some extra brain cycles on improving the look and feel, both when visualizing state but
also for emphasizing state transitions.

Reset by shaking the field

When we click the "reset" button, the dirty value is replaced by the last known base value and
a "shake" animation is played on the text field.

A shake is a rotation of the field around its center by an angle from -3 to +3 degrees. This is
done 3 times during 100 ms each. It makes for a funny effect.

final Transition fadeIn =


RotateTransitionBuilder.create().node(textField).toAngle(0).duration(Duration.millis(200)).build();
final Transition fadeOut =
RotateTransitionBuilder.create().node(textField).fromAngle(-3).interpolator(Interpolator.LINEAR).
toAngle(3).cycleCount(3).duration(Duration.millis(100)).
onFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent actionEvent) {
textAttributeModel.getAt(ATT_FIRSTNAME).reset();
fadeIn.playFromStart();
}
}).build();
reset.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent actionEvent) {
fadeOut.playFromStart();
}
});

Note that the transition is only created once but played as often as you click the button.

Yes, director!

The server (controller) part has been divided in two classes: the step6.TutorialAction that
contains only one application-specific action and the step6.TutorialDirector who selects which
actors should appear in the play, i.e. registers actions with the server dolphin.

This distinction makes it easier to evolve the application when new actions come into play
since the server adapter (servlet) doesn't have to change when the list of actions changes as
we will see in a minute.

3.7 Remote setup


With the application being properly structured in its modules, we can now finally distribute it as
a true client-server application without any of the application code being touched at all. Only
the the server adapter needs to be in place and the client starter needs to connect to the
correct URL.

31
The server adapter is a plain-old Servlet such that the code can run in any servlet container. It
is as small as can be:

public class TutorialServlet extends DolphinServlet{


@Override
protected void registerApplicationActions(ServerDolphin serverDolphin) {
serverDolphin.register(new TutorialDirector());
}
}

As with any servlet, you need to register it in the web.xml:

<servlet>
<display-name>TutorialServlet</display-name>
<servlet-name>tutorial</servlet-name>
<servlet-class>step_7.servlet.TutorialServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>tutorial</servlet-name>
<url-pattern>/tutorial/</url-pattern>
</servlet-mapping>

The step7.TutorialStarter can now move to the "client" module since it is no longer dependent
on the combination of client and server. It can be cleaned from setting up the in-memory
server and must of course point to the server URL:

public static void main(String[] args) throws Exception {


ClientDolphin clientDolphin = new ClientDolphin();
clientDolphin.setClientModelStore(new ClientModelStore(clientDolphin));
HttpClientConnector connector = new HttpClientConnector(clientDolphin,
"http://localhost:8080/myFirstDolphin/tutorial/");
connector.setCodec(new JsonCodec());
connector.setUiThreadHandler(new JavaFXUiThreadHandler());
clientDolphin.setClientConnector(connector);
TutorialApplication.clientDolphin = clientDolphin;
Application.launch(TutorialApplication.class);
}

That was it!

You can now start the provided jetty server

./gradlew :server-app:jettyRun

and the as many TutorialStarter clients as you want.

Alternatively, ou can now create a war file via Maven or Gradle and deploy it on any server
you fancy.

Some extra flexibility

You may have observed that we refactored the actual server-side printing into a service class
with a service interface. This allows some extra flexibility when the server-side action depends
on any technology that is only available on the server - say JEE, JPA, Spring, GORM, or so.

Refactoring that access into an interface allows to still use the same code with in-memory
mode for testing, debugging, profiling, and so on with a stub or mock implementation of the
service interface.

32
Final considerations
This has been a very small application to start with but we have touched all relevant bases
from starting with a standalone view, through proper modularization, up to a remote
client-server setup.

We have used a "bare-bones" setup with 100% pure Java and a no dependencies beyond
Java 7+ and OpenDolphin.

This is to show that OpenDolphin is as "un-opinionated" as can be.

In real life and in most of the demos that ship with OpenDolphin, we make additional use of
Groovy, GroovyFX, and Grails. Note that you can use any client- and server-side framework
and technology with OpenDolphin: Griffon, Eclipse RCP, Netbeans - JEE, Spring, Grails,
Glassfish, JBoss, Hibernate, WebLogic, WebSphere, you name it.

Remember: OpenDolphin is a library, not a framework.


We don't lock you in, we are open.

Of course, a full application has more use cases than managing a single text field.

The use cases and demos chapter leads you through the typical use cases of master-detail
views, form-based pages, collections of data, lazy loading, shared attributes, CRUD
operations, and much more by describing the use case, explaining the OpenDolphin approach
of solving it, and pointing to the respective demos.

33
4 Use Cases and Demos
OpenDolphin has been created from use cases.

This has always been very important to us.

Each functionality in OpenDolphin is motivated by a use case that justifies it's existence in the
code base. Likewise, each use case comes with a demo in the OpenDolphin distribution -
from simple, basic attribute bindings to complex feature combinations.

This chapter leads through the various use cases from simple to increasingly complex ones.

We explain
the use case
OpenDolphin's approach of solving it
how it is visible in the respective demos

4.1 The general approach of OpenDolphin with a Login dialog


The login demo is a short intro that explains the general approach of OpenDolphin without
any code.

Open this short video in a separate window: login demo

When watching the video, please pay attention to the following parts:
first, an action is registered on the server side
a presentation model is created on the client side with attributes for username, password,
and loggedIn status
the presentation model automatically sent to the server
the client creates a view for the login
the view binds against the attributes
when the user enters a name, this automatically changes the attribute on both client and
server side
the communication happens asynchronously; the user does not have to wait before
entering the password
in the very same manner, the password gets transferred
the user clicks on the login button
a command is sent to the server which triggers the login action
this reads username and password and sets the loggedIn status to TRUE (*)
the status change is automatically mirrored on the client
the login view disappears since its visible property is bound against the not-loggedIn
attribute

34
(*) technically, the server sends a ValueChangedCommand to the client but this is an
implementation detail.

4.2 Immediate and deferred binding


Use Case

Sometimes a value change in lets say a text field should immediately update all dependent
views (a label, a frame title) - but sometimes only when a certain event occurs: button clicked,
enter pressed, focus change and the likes.

Sometimes the binding is unidirectional, sometimes bidirectional.

Approach

OpenDolphin bindings are always immediate. When the source value changes the target
value is updated immediately.

If you need a deferred update, you do not use binding at all but provide the respective event
handler to copy the value from the source to the target, usually by setting the value of an
attribute.

Bindings are always unidirectional. If you need bidirectional binding, you use two binding
statements, one for either direction.

Demo

The demo looks like

When you edit the text field, the header is immediately updated.

Label and frame title are only updated when hitting enter in the text field or when clicking the
button.

Please see the full demo sources.

OpenDolphin bindings always go from source to target. This code makes sure that whenever
the title attribute of the presentation model changes, the title of the frame is updated:

35
bind TITLE of pm to FX.TITLE of primaryStage // groovy style

The same is true for the label, but here we are more Java-stylish:

bind(TITLE).of(pm).to(FX.TEXT).of(label) // java fluent-interface style

The input text field shall always show the value of the title attribute:

bind TITLE of pm to FX.TEXT of input

Hitting enter or clicking the button shall copy the value of the text input field to the attribute
and thus trigger update of all bound views. We share the same event handler for the onAction
of the text input field and the submit button:

def copyFieldToPm = { pm[TITLE].value = sgb.input.text } as EventHandler


sgb.input.onAction = copyFieldToPm
sgb.submit.onAction = copyFieldToPm

Corner case :
If we need a mixture of both immediate and deferred update, then we have a bit of a problem.
We can resolve it by directly binding the views (ignoring the attribute):

// auto-update the header with every keystroke


bind FX.TEXT of input to FX.TEXT of header

The views now act as one "combined" view, but this situation should be avoided since we do
not want the views to know each other. They shall only know their attributes.

Variant :
A second way of approaching the above is binding the text field back to the title attribute,
which will automatically trigger updates of all dependent views with every keystroke. This is
effectively a bidirectional binding:

// the below is an alternative that updates the pm with every keystroke and thus all bound listeners
// bind TEXT of input to TITLE of pm

4.3 The container yard monitoring application


Please have a look at this short video in a separate window: container yard demo

You see a monitoring application that has been implemented in a joint effort by
Navis
Oracle
Canoo

and was demonstrated at the JavaOne 2012 strategy keynote.

36
This application uses JavaFX on the client side and builds upon an existing server-side
programming model.

OpenDolphin combines these two worlds.

The application was first developed in a purely 2-D fashion and later extended to 3-D.

Despite the rather dramatic effect that this extension had to the user interface, the
presentation models did not change by a single bit!

This example demonstrates:


the value of having the full "fidelity" of a Java desktop client
how to leverage the power of Java server-side applications
how OpenDolphin helps to stay independent of changes in visualization and protects the
investment in application logic

37
5 tbd Configuration and Setup
5.1 Standalone in-memory usage

5.2 Remote setup

38
6 tbd Developer Zone
6.1 How to build

Copies of this document may be made for your own use and for distribution to others, provided
that you do not charge any fee for such copies and further provided that each copy contains this
Copyright Notice, whether distributed in print or electronically.

39

Vous aimerez peut-être aussi