Dolphin Guide
-
Upload
mhenley8059 -
Category
Documents
-
view
43 -
download
5
Transcript of Dolphin Guide
1
Dolphin - where Enterprise Java meets Desktop Java
Dolphin - Reference DocumentationAuthors: The Dolphin team
Version: PRE-1.0
Table of contents
2
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
3
1 Introduction initiated the Dolphin project since we had recognized that many of our customersCanoo
share a common quest:Keeping the on the but it with all the capabilities of the application logic server presenting
device.client
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 presentationmodel followed by some introductory slides on the topic.
In terms of Model-View-Controller (MVC) one can see in the figure above that Dolphin putsthe responsibility on the client but leaves the logic on the server. The View controlling model(here in the sense of a model, not a domain model) is shared.presentation
This is shown in even more detail below.
4
5
Throughout this documentation, we will refer to this introduction and especially the section on expands on the value of clean structures.architecture
1.1 Latest Changes
Release 0.8
25 issues have been addressed in this release where the most important ones come from ourusers that already have OpenDolphin-based applications in production. This ranges fromperformance-oriented topics like the support for client-side models, command batching andcompressing down to operational support to e.g. detect unwanted breaches of the sessionaffinity.
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 to comply with the conventions on maven central.org.open-dolphin
The project artefacts are now available for download from MavenCentral
Please refer to for an example on how to use OpenDolphin with eitherDolphinJumpStartMaven 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.
6
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 distributedthrough 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 OpenDolphincode 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 updateinstantly and consistently
TrainControlDemo where a dash board changes the train speed and other clients arenotified
LazyLoadingDemo with 100'000 rows in a table
PortfolioDemo where the management of a portfolio serves as a typical example of abusiness application
Version 0.5 upgraded dependencies to Groovy 2.1.1. and GPars 1.0. We make use of thenew @CompileStatic where appropriate and enhanced execution speed by 60%.
Release Notes - Dolphin - Version 0.5
Release 0.4 : Notable Changes since 0.3
7
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
1.
2.
3.
4.
5.
DOL-25 - provide a demo that measures dolphin response times
DOL-33 - allow more meta-infos per Attribute
DOL-19 - Provide a code example that shows how to use dolphin for lazy loading
DOL-21 - Build does not automatically discover javafx in JDK when JAVAFX_HOME isset
DOL-37 - align server and client side API for CRUD operations on presentation modelsand attributes
DOL-38 - nicer API for server side value change
DOL-39 - API enhancement: server actions should work transparently on the response
DOL-40 - API enhancement: allow finding presentation models and attributes without theneed for casting in both Java and Groovy friendly manner
DOL-41 - start a user guide
DOL-42 - provide combined apidoc: javadoc / groovydoc
DOL-43 - Provide a DataCommand
DOL-44 - Provide a more complex demo (dependencies, crud operations, remoting)
DOL-17 - provide a 'gradlew run' to start any demo
DOL-18 - link the website to the latest version of the documentation
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 ( and ) facadeClientDolphin ServerDolphinwhenever possible. This also true for all the cases where application code on the server sidedirectly invokes constructors of commands, presentation models or attributes. Please usefactory methods instead.
Solved Bugs
DOL-15 - Basic build fails
DOL-22 - exception in reference table demo and search demo
DOL-26 - Binding the text property of a Swing component to a ClientPresentationModelthrows Exception
DOL-31 - ServerDolphin.createPresentationModel has an "optional" parameter, which isrequired
DOL-35 - com.canoo.dolphin.demo.startCreatePresentationModelDemo.groovy still triesto invoke serverDolphin.createPresentationModel
8
1.2 CreditsA big goes to all our early-adopting users that have provided us with muchthank youappreciated feedback, inspiration, and other support, particularly
Oracle
Navis
Knappschaft Bahn-See
Siemens
Inventage
and other companies that prefer to remain unnamed.
9
1.
2.
1.
2.
3.
2 ArchitectureThis user guide first explains the general approach of OpenDolphin and its architecture beforegoing into details and examples.
Reading through the user guide in this sequence is for those who like to first understandthings on a general level.
Others prefer to start with concrete demos and use cases, which is just as legitimate. In thiscase, you are welcome to skip the architecture section and directly jump to the .demos
The Dolphin architecture is made from two parts:
the structure
concepts and rules
2.1 The OpenDolphin structure
Modules define the vertical structure
Dolphin consists of three parts:
the shared module
the client module
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 modulesabove 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 buildsystem is used to build them according to their dependency structure.
Each project that uses Dolphin will most likely resemble this structure where the sharedmodule may be empty (in case they is no shared knowledge between client and server).
The Dolphin team has produced a that suggests a best-practice projectJumpStartProjectsetup 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 ishighly recommended to have a look at the demos in order to understand how we expectDolphin to be used.
The horizontal layering
10
Orthogonally to the vertical structure of modules, there is a simple horizontal layering thatspans 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, theconcepts and rules that stand behind its inception require a bit of explanation, which is thetopic of the next section.
2.2 The concept of presentation modelsPresentation models are probably best known because of the presentation model design
by Martin Fowler.pattern
They also come under different names like or .Application Model View Model
There is a lot of information about presentation models on the web, e.g.
11
from Microsoft Developer Network
from Canoo
from JGoodies
Dolphin doesn't claim to have "the best" implementation of this pattern in any sense of theword.
In fact, we do not even claim to implement the "presentation model design" at all! pattern
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 theirwork.
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 should be visualized (as opposed to ).what 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 s (like many but not all other implementations).Attribute
They support declarative binding between attributes and GUI elements.
What is special
12
Dolphin presentation models are . You never implement a new class for a newgenerictype of presentation model.
They do not contain any custom behavior (only what's provided by Dolphin for bindingand remoting).
They allow .stable bindings
They are Their attributes contain no object references, only primitive values. There isflat.no object graph.
In particular, they do not maintain an object reference to a domain model (but maybe akey).
They are between a client and a server. Dolphin cares for consistentsharedsynchronization.
They live in a managed object space. All instances are known and can be retrieved bytheir characteristics.
They have a unique , which either the application programmer provides or isidautomatically assigned otherwise.
They optionally have a parameter for logical grouping.type
See also: and .Usage API doc
Where the logic goes
When there is the least bit of business logic, it goes into an action. Actions live in controllerclasses of the server module.
There is a small fraction of logic that resides on the client, which is logic like e.g. forviewcalculating component bounds when layouting.
2.3 The purpose of attributesEach presentation model can refer to many attributes.
Attributes maintain no back-reference. Technically, they can be shared between presentationmodels.
Attributes are You never need implement your own attribute type.generic.
Each attribute has a unique and a that is unique per presentationid propertyNamemodel.
Attributes encapsulate a primitive value and allow listeners to be notified about valuechanges. Technically, this is done via the standard Java PropertyChangeListenertype. In other words, attributes are .Observable
In addition to the value, attributes provide more information about the value, e.g. whether thevalue is (i.e. it differs from the ). The dirty state is observable such that youdirty base valuecan bind against it. As soon as at least one attribute of a presentation model is dirty, thatpresentation model is also considered dirty.
13
Attributes have an optional property to capture the fact that this attribute represents aqualifierqualified feature in the domain world. A qualifier like "person.4711.firstName" may be used toexplain that this attribute represents the value of the property of firstName Persondomain class with id 4711.
Whenever the value of an Attribute changes, all other attributes with the same qualifier areautomatically updated.
Qualifiers are a prerequisite for .stable bindings
Since version 0.4 Dolphin attributes can be tagged in order to describe the meaning of thevalue. The enumeration provides all possible tags with being the default. TagsTag VALUEcan be used to e.g. describe whether the person firstname is a attribute,MANDATORYwhether it is , , and so on. Attributes with a tag are observable in theENABLED VISIBLEusual way.
See also and .Usage API doc
2.4 Stable bindings is just another word for an event listener listening to an event source.Binding
OpenDolphin supports binding between attributes and arbitrary other objects where most ofthe time, these other objects are views - with the notable exception of the dolphininfrastructure 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 withevery technology that understands PropertyChangeListeners (e.g. SWT and Eclipse RCP), itprovides special binding facilities for
JavaFx and
Swing.
Bindings can work in both directions such that a view automatically reflects any change in anattribute value or such that an attribute value is automatically updated with every user input toa 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 usecases where a selection changes the to-be-displayed information, commonly known asmaster-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 presentation modelplaceholderthat is automatically synchronized (both read and write) with the selection.
14
The detail view has to change its binding!never
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 storeAny reasonably sized UI will have not only single presentation models but many of the sametype.
Let's assume that the application displays a list of vehicles where each vehicle is apresentation model instance of type "vehicle".
The list view must add a new row whenever a new vehicle becomes available and mustremove the row when the vehicle becomes unavailable.
To this end, the list view can register itself as a and will beModelStoreListenernotified 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 automaticallysynchronized by OpenDolphin.
Conceptually, one can see the model store as
a distributed, in-memory, no-sql database with only two tables (presentation model andattribute)
a specialized event bus (event provider is only the store itself and only ModelStoreEventsare issued)
The application programmer should not access the model store directly but only through thefacade 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 modelA dolphin application has a client and a server module. As we will see in and remote config
this does not necessarily mean that client and server live in different VMs,standalone configbut they can.
With OpenDolphin, the client is responsible to visualize presentation models, the server isresponsible to manage the presentation model data.
In order to keep client and server consistent, one must be chosen as the mastermodel storeand the dolphin team has chosen the client to be that master.
15
All changes happen . Then the server is notified, and he updatesfirst on the clientautomatically.
Any server-side changes happen by the server sending a to the client instructingcommandhim which change to apply.
All communication between client and server happens no matter whether anasynchronouslyin-memory or remote configuration is used.
Despite the asynchronous communication, OpenDolphin guarantees notification delivery inthe same sequence as they originated.
OpenDolphin assumes the client to have an event model and the server to live in arequest-response model. OpenDolphin bridges these two worlds.
Despite the asynchronous communication between client (view) and server (controller), allprocessing that takes place as a result of this communication is automatically executed insidethe client's UI thread. Especially all changes to the client model store, client presentationmodels, and attributes are subject to this thread-confined approach. Thus, all binding eventswill 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 theapplication a lot of otherwise common headaches around proper thread handling disappear.
2.7 Communicating via CommandsAll 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 forsynchronization 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 wasregistered 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 (e.g. JSONCoDec).codec
A (e.g. HttpTransport or InMemoryTransport) can be configured to use such a transport codec.
Commands are used as a unit of transport since they are simple to encode/decode but alsosince they can be optimized in a number ways (buffering when the server is temporarilyunavailable, batching, elimination of duplicates) and they open the opportunity for laterinclusion of undo/redo capabilities.
16
Since commands are sent asynchronously, one cannot wait for a command to complete.When sending a command, though, one can provide an that isOnFinishedHandlercalled back as soon as the command has returned. The list of all presentation models thatwere affected by that command is passed as argument into the callback methods.
See also .Usage
2.8 Relying on the command sequenceWe have seen that OpenDolphin uses commands for the communication between views andcontrollers, that commands are sent in strict sequence even though they are executedasynchronously, and that commands may have an attached to it.onFinishedHandler
It goes without saying that any onFinished handler is only executed after the controller actionis finished. Therefore the name.
But despite the sequence guarantees that OpenDolphin gives you, there are a few things toconsider about the asynchronous programming model where it is inherently different fromprocedural, 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 independentcommands A, B, and C are sent in immediate sequence. Time goes from top to bottom andthe three columns represent three concurrent processing units: the command queue, thecontroller actions (can even be remote), and the onFinished activity after the action returned.
A, B, and C sit in the command queue (column 1).1)
A is removed from the queue and its actions are processed outside the UI thread, maybe2)even remotely on the server (column 2). B and C remain in the queue (column 1).
All A actions are finished and the onFinished handler is called (column 3). 3) While B was removed from the queue and the BA.onFinished is executed inside the UI thread
actions are processed concurrently outside the UI thread (column 2).
17
The execution of A.onFinished often triggers further commands (A1 and A2) that are fed intothe queue (column 1) just like any other ordinary command.
At this point the B actions are processed without the effects of A beingvisible.B must not depend on the effects of A!
Emptying out the command queue.4, 5, 6, 7)
Let's summarize the behavior so far
commands are always processed in the strict sequence in which they appear in thecommand 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 notnecessarily 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 maypossibly spawn before processing B like in procedural programming? . This would meanNothat we have to block the queue during that time, which in turn would block the UI - andblocking the UI is the worst you can do.
" ?" This is when we need the onFinishedHandler asBut what if B depends on the effects of Aexplained 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. ThenB depends on A and we have to make sure that at the time when the B action is processed alleffects 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 Acommand. 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"
18
The figure below shows the resulting command sequence:
Note row number . Only in the course of A.onFinished being executed (column 3) is the B3command added to the command queue (column 1) and A1 and A2, whichguaranteed afternotify the server of the created presentation model. So all relevant state is properly updatedwhen B's actions ( ) and onFinished handler ( ) are called.6 7
Practical considerations
Asynchronous programming models all have in common that when some logic is dependenton 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 automaticallylead to deeply nested callback structures that are difficult to write and understand? Notnecessarily.
Various solution are on the market (e.g. "promises") but OpenDolphin has a simple waywhere 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 suchthat we can add us to the command queue at the appropriate time when all is ready. Youguessed it: such a command already exits. It is the EmptyCommand and you can send it viathe ClientDolphin's method.sync
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"
19
Likewise, when B and C depend on A but not on each other (A <- B, A <- C) you can code thisas
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 withoutany other command possibly sneeking in between the two, no matter whether it originatesfrom user input or preceding commands. In that sense you can see the as enclosingsyncan atomic operation.
See also .Usage
2.9 DiscussionNo architecture documentation is complete without explaining the rationale behind thedecisions - 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 whendesigning 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, SupervisingController, Event Bus, and many more.
All such patterns have their benefits and particularly a distributed event system would haveplayed well with the intended remoting capabilities.
It were mainly the good experiences we made with the presentation model pattern in anumber of large event-based systems that it became our first choice. The distinction between
and turned out to be easier to explain than other approaches. It always gave thewhat howteam good guidance when developing the system. Also we had the honor to have thegrandmaster of this pattern Dr. Dieter Holz in the team.
Following the was always easy enough even though it required writing the respectivepatternclasses all over again with each new project. As soon as we found out how to generalize thepattern 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 stillaround under the name ) turned out to become really complex and not reliableGRASPenough to build solid remoting upon because of the intricacies of "rebinding" when switchingreferences to attributes.
Let's say a text field should bind against the first name of person A. Then you switch toperson B. Now try to think through all the implications that this has in terms of consistentlyrebinding all views and other listeners and informing the server. We lost many a night's sleepover this seemingly simple issue. And it gets worse when you don't have a genericimplementation.
20
As soon as Andres Almiray discovered how to do stable bindings, everything seemed to justfall 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 customstate and behavior.
With generic implementations, we not only have the benefit of fewer classes in the system butmore importantly less structural duplication.
We also avoid all the versioning problems that inevitably arise with shared application classesbetween client and server.
But most of all, with generic implementations it is much easier to control the system. Withsubclasses, you never quite know what they are doing in extend and whether e.g. it is nowsafe 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 youexactly know what they are and what they do, it is much easier to build convenience methodsin 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 (weband mobile).
Why not simply using REST?
OpenDolphin is actually free to use a REST if so configured (while the currenttransportHttpTransport is actually using only the POST method and therefore doesn't really qualify aspure REST).
Even though these are not necessarily constituent features of REST, people often understandREST 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 allowto 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 actaccordingly, 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
21
With the application running on the server, OpenDolphin is in full control what the clientdisplays - 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 newbehavior.
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 onthe server side, having a bigger memory footprint and affecting horizontal scaling with theneed 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.
22
3 How to get started with OpenDolphinFor an easy entry into OpenDolphin, we will follow the steps of the project.DolphinJumpStart
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 theserver side.
Both buttons are only enabled if there is really something to save/reset, i.e. the field value isdirty. The dirty state is also visualized via a CSS class (background color changes). Resettingtriggers a 'shake' animation.
Steps 0 to 4 solely live in the "combined" module for a simple jumpstart before we properlysplit 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 tothe "client" module.
Setup and start of a basic JavaFX view
Let's start with the setup.
Please make sure you have visited the project and have looked at theDolphinJumpStartreadme.
You can either choose to clone the repo for following each step (recommended) or use theprovided 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 aproject. All major IDEs should understand this.
In case you are not using any IDE, follow the readme for how to build and run the varioussteps.
We start our development in the "combined" module with the simplest possible JavaFX view . The class has a main method such that you can start it from inside the IDE.step0.JumpStart
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.
23
public class JumpStart Application {extends
@Override void start(Stage primaryStage) Exception {public throws primaryStage.setScene( Scene( Pane(), 300, 100));new new primaryStage.setTitle( );"Dolphin Jump Start" primaryStage.show(); }
void main( [] args) {public static String 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 - stillwithout any OpenDolphin specifics.
3.1 Adding nodes to a stage, registering an onAction handlerWe stay in the "combined" module and enhance the JavaFX view . juststep1.JumpStartslightly 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; Button button;private
and an action handler
button.setOnAction( EventHandler<ActionEvent>() {new void handle(ActionEvent actionEvent) {public .out.println( +field.getText());System "text field contains: " }});
The printing of the field content is our "stand-in" for a real business logic. You can easilyassume some persistence action at this point or "service" calls in general.
Now it is time to introduce OpenDolphin.
24
3.2 Introducing a presentation model with one attribute and bindthe valueIn step 2 we refactor the JavaFX application into to make use ofstep2.JumpStartOpenDolphin.
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 sameplace. 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( , ClientAttribute( ));"input" new "text"
Note that we define a "JumpStartPresentationModel" or so since presentation models innotOpenDolphin 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 beingupdated)
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 observein 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( ).of(field).to( ).of(input);"text" "text"
25
Note the fluent API for setting up the binding.
The above is plain Java. When you use Groovy, you can make use ofGroovy's command chain syntax that allows writing the exact same codeas
bind of field to of input"text" "text"
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( , NamedCommandHandler() {"PrintText" new void handleCommand(NamedCommand namedCommand, List<Command> commands) {public text = serverDolphin.getAt( ).getAt( ).getValue();Object "input" "text" .out.println( + text);System "server text field contains: " }});
Note that the (client) view and the (server) controller do not share anyobjects!
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 onActionhandler.
button.setOnAction( EventHandler<ActionEvent>() {new void handle(ActionEvent actionEvent) {public clientDolphin.send( );"PrintText" }});
When we now start the application we see in the log:
[INFO] C: transmitting Command: CreatePresentationModel pmId input pmType attributesnull[[propertyName:text, id:761947653, qualifier: , value: , tag:VALUE]]null null[INFO] S: received Command: CreatePresentationModel pmId input pmType attributesnull[[propertyName:text, id:761947653, qualifier: , value: , tag:VALUE]]null null[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 anempty String, the JavaFX default value for text fields.
Let's enter "abcd":
26
[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: PrintTextserver 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 , particularly not in the UI thread!asynchronouslyYou can see the asynchronous behavior by the line ordering in the log above. If it weresynchronous, 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 fromOpenDolphin:
All actions are executed .asynchronously outside the UI threadWe cannot accidentally block it by long-running or failed operations, whichis a common error in UI development.
With the first dolphinized application running, let's clean up and add a bit more OpenDolphingoodness.
3.3 Logical separation between client and serverIn we first cleanup the code such that it becomes more obvious, which partstep3.JumpStartbelongs to the (client) view and the (server) controller. In the first place, OpenDolphin leads toa view-controller distinction and client-server split. The only thing that is optionallylogicalshared are constants.
It is always a good idea to refactor literal values into constants, especially if they are used inmore than one place for a unique purpose. Therefore, pull out our String literals into staticreferences:
private MODEL_ID = ;static final String "modelId" MODEL_ATTRIBUTE_ID = ;private static final String "attrId" COMMAND_ID = ;private static final String "LogOnServer"
The configuration setup should move into the constructor:
27
public JumpStart() { config = DefaultInMemoryConfig();new textAttributeModel = config.getClientDolphin().presentationModel(MODEL_ID, newClientAttribute(MODEL_ATTRIBUTE_ID, ));"" config.getClientDolphin().getClientConnector().setUiThreadHandler( JavaFXUiThreadHandler());new config.registerDefaultActions();}
This leaves the "start" method with "view" responsibilities only: the initial contruction andseparate method call for binding and registering actions.
@Override void start(Stage stage) Exception {public throws
Pane root = PaneBuilder.create().children( VBoxBuilder.create().children( textField = TextFieldBuilder.create().build(), button = ButtonBuilder.create().text( ).build(),"press me" HBoxBuilder.create().children( LabelBuilder.create().text( ).build(),"IsDirty ?" status = CheckBoxBuilder.create().disable( ).build()true ).build()
).build() ).build();
addServerSideAction(); addClientSideAction(); setupBinding();
stage.setScene( Scene(root, 300, 100));new stage.show();}
We add an additional labeled checkbox to visualize the status: whether the text field - orbetter 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. Ifyou remove your edits, it should become unselected again!
Here is how the binding for that requirement looks like:
JFXBinder.bind( ).of(textField).to(MODEL_ATTRIBUTE_ID).of(textAttributeModel);"text"JFXBinder.bindInfo( ).of(textAttributeModel.getAt(MODEL_ATTRIBUTE_ID)).to( ).of(status);"dirty" "selected"
At this point we see the next benefit of presentation model and attribute abstractions: they canprovide more information about themselves and can carry additional state that is automaticallyupdated and available for binding.
Each attribute has a "base" value. When the current value differs from that base value, it isconsidered "dirty". A presentation model is dirty, if and only if any of it's attributes is dirty.
28
With this knowledge, we can even do a little more.
3.4 Bind the "dirty" of presentation models to the viewIn we make even further use of the bindable dirty state.step4.JumpStart
First, we are binding not against the dirty state of an attribute, but against the wholepresentation model behind it. This simplifies the binding:
JFXBinder.bindInfo( ).of(textAttributeModel).to( ).of(status);"dirty" "selected"
Second, we also want the button to only be enabled when there is something reasonable todo, i.e. when there is some value change in the form. This is a very common requirement inbusiness applications.
Now, JavaFX buttons do not have an "enabled" state, only a "disabled" state with the oppositelogic. Luckily, our binding facilities are perfectly able to handle this with a converter:
JFXBinder.bindInfo( ).of(textAttributeModel).to( ).of(button, Closure( ) {"dirty" "disabled" new null doCall( dirtyState) {protected Object boolean !dirtyState;return }});
The converter is a Closure (coming from Groovy). But no worries. We can perfectly use it inJava. 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 of textAttributeModel to of button, { state -> !state }"dirty" "disabled"
Once the code is so nicely broken into independent parts we can put the various parts inseparate modules for better dependency management.
3.5 Split into modules/projectsStep 5 distributes the code into multiple modules (IntelliJ IDEA parlance) orprojects/subprojects (Gradle, Maven, Eclipse parlance). We use the more generic word"module".
The module depends on both client and server and is used for starting thecombinedapplication with the configuration. The sole class that lives in this module is thein-memorystarter class . It sets up the configuration, registers thestep5.TutorialStarterapplication-specific actions on the (server) controller, and starts the view. This is the class tostart from inside the IDE.
The module (or "view" module if you wish) contains the view.client step5.TutorialApplication
29
You can see that the view code is pretty much the same as our old application code butcontains the view specific parts only. There is one additional change, though. When thebutton has been pressed and the command has been executed on the server we would like tointerpret the current content of the text field as the new base value just as if the error-freeexecution of the command would imply a correct "save". The "disabled" state of the button willreflect the new non-dirty state.
To this end, we make use of an handler:onFinished
public void handle(ActionEvent actionEvent) { clientDolphin.send(CMD_LOG, OnFinishedHandlerAdapter() {new @Override void onFinished(List<ClientPresentationModel> presentationModels) {public textAttributeModel.getAt(ATT_FIRSTNAME).rebase(); } });}
Please note that the onFinished handler will be called the UI thread. Itasynchronously insidemay trigger changes in the model store, which may lead to changes in the display and anysuch changes occur in the UI thread.must
The module (or "controller" module if you wish) contains the server step5.TutorialActioncontroller.
Both, client and server depend on the module, which makes the shared known to both parties. The shared module itself does not depend onstep5.TutorialConstants
anything.
The code that contains the shared constants now also cares for the uniqueness of certainStrings, particularly of IDs used to retrieve presentation models and named commands.
public PM_PERSON = unique( );static final String "modelId" ATT_FIRSTNAME = ;public static final String "attrId" CMD_LOG = unique( );public static final String "LogOnServer"
unique( key) {private static String String TutorialConstants.class.getName() + + key;return "."}
Splitting four classes into four different modules may look a bit over-engineered at this pointbut it is an indispensible step before we can go into true remoting and before we can instantlyswitch 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-timedependencies: simply unzip one of the and you are good to go.project templates
We get the following benefits:
30
ability to start the code with different configurations (in-memory orunmodifiedclient-server)
clear and minimal dependencies when building
a minimum of shared code (only the constants) to express semantic dependencies assyntatic dependencies
actions cannot "accidentally" reach out to view code. The widget set is not even on theclasspath!
actions cannot possibly block the UI thread
view changes are displayed correctly since they happen in the UI threadalways
the separation of responsibilities is enforced by the dependency structure
3.6 Enhanced view, let the "director" wire all application actionsWe finish the application with some more refactorings in and somestep6.TutorialApplicationtweaks 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 setback 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( ).of(textAttributeModel).to( ).of(textField, Closure( ) {"dirty" "style" new null call( dirty) {public String Boolean (dirty) {if 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 shouldwe later decide to visualize the dirty state differently.
31
.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 canspend some extra brain cycles on improving the look and feel, both when visualizing state butalso 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 anda "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 isdone 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();
Transition fadeOut =finalRotateTransitionBuilder.create().node(textField).fromAngle(-3).interpolator(Interpolator.LINEAR). toAngle(3).cycleCount(3).duration(Duration.millis(100)). onFinished( EventHandler<ActionEvent>() {new @Override void handle(ActionEvent actionEvent) {public textAttributeModel.getAt(ATT_FIRSTNAME).reset(); fadeIn.playFromStart(); } }).build();
reset.setOnAction( EventHandler<ActionEvent>() {new @Override void handle(ActionEvent actionEvent) {public 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 thatstep6.TutorialActioncontains only one application-specific action and the who selects whichstep6.TutorialDirectoractors 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 playsince the server adapter (servlet) doesn't have to change when the list of actions changes aswe will see in a minute.
3.7 Remote setupWith the application being properly structured in its modules, we can now finally distribute it asa true client-server application without any of the application code being touched at all. Onlythe the server adapter needs to be in place and the client starter needs to connect to thecorrect URL.
32
The is a plain-old Servlet such that the code can run in any servlet container. Itserver adapteris as small as can be:
public class TutorialServlet DolphinServlet{extends @Override void registerApplicationActions(ServerDolphin serverDolphin) {protected serverDolphin.register( TutorialDirector());new }}
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 can now move to the "client" module since it is no longer dependentstep7.TutorialStarteron the combination of client and server. It can be cleaned from setting up the in-memoryserver and must of course point to the server URL:
public void main( [] args) Exception {static String throws ClientDolphin clientDolphin = ClientDolphin();new clientDolphin.setClientModelStore( ClientModelStore(clientDolphin));new HttpClientConnector connector = HttpClientConnector(clientDolphin, new
);"http://localhost:8080/myFirstDolphin/tutorial/" connector.setCodec( JsonCodec());new connector.setUiThreadHandler( JavaFXUiThreadHandler());new 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 serveryou fancy.
Some extra flexibility
You may have observed that we refactored the actual server-side printing into a service classwith a service interface. This allows some extra flexibility when the server-side action dependson 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-memorymode for testing, debugging, profiling, and so on with a stub or mock implementation of theservice interface.
33
Final considerations
This has been a very small application to start with but we have touched all relevant basesfrom starting with a standalone view, through proper modularization, up to a remoteclient-server setup.
We have used a "bare-bones" setup with 100% pure Java and a no dependencies beyondJava 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 ofGroovy, GroovyFX, and Grails. Note that you can use any client- and server-side frameworkand 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 chapter leads you through the typical use cases of master-detailuse cases and demosviews, form-based pages, collections of data, lazy loading, shared attributes, CRUDoperations, and much more by describing the use case, explaining the OpenDolphin approachof solving it, and pointing to the respective demos.
34
4 Use Cases and DemosOpenDolphin 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 thecode 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 dialogThe login demo is a short intro that explains the general approach of OpenDolphin withoutany 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 sent to the serverautomatically
the client creates a view for the login
the view binds against the attributes
when the user enters a name, this changes the attribute on client andautomatically bothserver side
the communication happens asynchronously; the user beforedoes not have to waitentering 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-loggedInattribute
35
(*) technically, the server sends a ValueChangedCommand to the client but this is animplementation detail.
4.2 Immediate and deferred binding
Use Case
Sometimes a value change in lets say a text field should immediately update all dependentviews (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 targetvalue is updated immediately.
If you need a deferred update, you do not use binding at all but provide the respective eventhandler to copy the value from the source to the target, usually by setting the value of anattribute.
Bindings are always unidirectional. If you need bidirectional binding, you use two bindingstatements, 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 thebutton.
Please see the full .demo sources
OpenDolphin bindings always go from source to target. This code makes sure that wheneverthe title attribute of the presentation model changes, the title of the frame is updated:
36
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- styleinterface
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 attributeand thus trigger update of all bound views. We share the same event handler for the onActionof the text input field and the submit button:
def copyFieldToPm = { pm[TITLE].value = sgb.input.text } as EventHandlersgb.input.onAction = copyFieldToPmsgb.submit.onAction = copyFieldToPm
:Corner caseIf 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 keystrokebind 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 donot want the views to know each other. They shall only know their attributes.
:VariantA 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 iseffectively 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 applicationPlease 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.
37
This application uses JavaFX on the client side and builds upon an existing server-sideprogramming 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, thepresentation 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 theinvestment in application logic
38
5 tbd Configuration and Setup
5.1 Standalone in-memory usage
5.2 Remote setup
39
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, providedthat 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.