tag:blogger.com,1999:blog-40086009506046474272024-02-07T01:40:54.596-08:00Bob's Biz BuzzAll the buzz that's biz to BobBob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.comBlogger12125tag:blogger.com,1999:blog-4008600950604647427.post-60396776456225778262019-12-20T12:15:00.000-08:002019-12-20T12:15:25.257-08:00And....we're back!I thought I was ready to retire when I resigned my job at Red Hat, but I've been sucked back in to the melee that is software development. Watch this space for some fun new projects and tutorials.<br />
<br />
Here's a little preview:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF_hmTTMT-V0R_3ljaDju3gzGizS8Q-2Z9pCHuR15L-UBOBmDzylvC4BSrJG1lB-Z_zAN6on_nSBNNw_0aBDnmO7r-Mk5NTZ94SxblR7vGGU7WQdSJTIMt5rvcUgB61T9xpeNDfLLRuSw/s1600/SnowmanFitbitWatch.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="584" data-original-width="520" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF_hmTTMT-V0R_3ljaDju3gzGizS8Q-2Z9pCHuR15L-UBOBmDzylvC4BSrJG1lB-Z_zAN6on_nSBNNw_0aBDnmO7r-Mk5NTZ94SxblR7vGGU7WQdSJTIMt5rvcUgB61T9xpeNDfLLRuSw/s320/SnowmanFitbitWatch.gif" width="284" /></a></div>
<br />
Happy Holidays everyone!Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0tag:blogger.com,1999:blog-4008600950604647427.post-79918815733233392632016-05-19T13:20:00.000-07:002016-05-19T11:56:48.059-07:00Uberfire - The Unanswered Questions: Part 1<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIEK5VBGsldUq6usqqJ3qPzEf_Vr-3PcAVaRIWTJUfkdTnMeysVi90oDbPxfmUVrKeMS1v4c3bJUUnPRks1HgKtSs5gVVg2XG3B3ooUC-d4klo9fzuW2rUHwwf4BMIlrOhEpxMKQ34bb0/s1600/uber-fire.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIEK5VBGsldUq6usqqJ3qPzEf_Vr-3PcAVaRIWTJUfkdTnMeysVi90oDbPxfmUVrKeMS1v4c3bJUUnPRks1HgKtSs5gVVg2XG3B3ooUC-d4klo9fzuW2rUHwwf4BMIlrOhEpxMKQ34bb0/s1600/uber-fire.png" /></a></div>
<h2>
Uber...what now? </h2>
<div>
Yes, I know it sounds like an unfortunate incident on a California freeway, but Uberfire is actually a Rich Client Platform for building web applications. Based on the <a href="http://www.gwtproject.org/overview.html">Google Web Toolkit</a> (GWT) and <a href="http://erraiframework.org/">JBoss Errai</a>, Uberfire allows you to build workbench-style applications entirely (or rather, mostly) in Java. Think of HTML meets Java with a little Javascript thrown in for good measure. Uberfire is currently used for the <a href="http://www.kiegroup.org/">KIE Workbench</a>, which has been a driving force in Uberfire's development. BTW, this actually happened to me in college - engine fires were simply accepted as part of the VW Beetle owner experience.<br />
<h2>
Disclaimer:</h2>
Uberfire has a steep learning curve and I'm still climbing that hill. The information in these blogs is my understanding of the software and may or may not be correct. I'm asking my readers to help me out and correct errors or misunderstandings here - thank you :) So with that said, please take heed of my disclaimer*.</div>
<h2>
Building on UF Tasks</h2>
If you haven't realized it by now, Uberfire is <strong>huge</strong> as far as application frameworks go, and it tries very hard to hide many of the complexities of web app development.<br />
<br />
While the <a href="https://github.com/uberfire/uberfire-tutorial">original UFTasks tutorial </a>does a good job of presenting some of the major UI framework concepts, there are many aspects of Uberfire which, in my mind at least, were still a mystery and left me wanting more. Some of the unanswered questions that I was thinking about while working through the tutorial were:
<br />
<ul>
<li>How does the VFS work, and what are my options for using the client <strong>and</strong> server side file system? I would have liked to be able to persist the Project/Folder/Task hierarchy that was developed by the UFTasks tutorial. I assume that since the tutorial web app requires a user login, how about a personal task list for each user? How do I get the currently logged-in user's information?</li>
<li>What is this <a href="http://docs.oracle.com/javaee/6/tutorial/doc/giwhl.html">CDI thing</a> you speak of? Even though I've been a java developer for years (I come from a C++ background) I have never really dug very deeply into CDI nor understood all of its complexities. Uberfire depends very heavily on using CDI for intra-app communication, so I was going to have to study up on it.</li>
<li><a href="https://en.wikipedia.org/wiki/Shm-reduplication">Views, shmiews</a>...what I really want is an Editor. Editors are a big part of the Uberfire user experience, but what exactly is a @WorkbenchEditor and how is it different from a @WorkbenchPartView?</li>
</ul>
<div>
I'm only scratching the surface of my "unanswered questions" in part 1 of this blog. As I become more familiar with Uberfire, I'm sure I'll find more unanswered questions along the way.</div>
<h3>
Rethinking the Model</h3>
<div>
Thinking about what a task or to-do list really is, the UFTasks demo left me wanting more than just a task name. What about priorities? Start and completion dates? Maybe a more detailed description? I can probably come up with several other things I would want in my task manager, but let's not lose sight of the goal, which is to learn about Uberfire, and not to design the <a href="http://www.wired.com/2016/03/best-to-do-list-app/">World's Greatest Freaking To-Do App</a>.</div>
<div>
<br /></div>
The first thing that struck me about the UFTasks tutorial was that the Model part of the <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter">MVP pattern</a> was scattered through some of the Presenter and View bits. This was a little infuriating because of, well... MVP! The Model should be contained in Model classes, not View or controller classes.<br />
<br />
Also, if I was going to persist the model, I would need the ability to quickly and easily navigate it and gather up all the related bits for serialization. So without further ado, here are my model classes:<br />
<h4>
TreeNode.java</h4>
<pre class="brush:java;">package org.uberfire.shared.model;
import java.util.ArrayList;
import java.util.List;
public abstract class TreeNode< PARENT extends TreeNode, CHILD extends TreeNode > {
private PARENT parent;
private List< CHILD > children;
public TreeNode() {
parent = null;
}
abstract public String getName();
public PARENT getParent() {
return (PARENT) parent;
}
protected void setParent(PARENT parent) {
this.parent = parent;
}
public List< CHILD > getChildren() {
if (children == null)
children = new ArrayList< CHILD >();
return children;
}
public void addChild(CHILD child) {
getChildren().add(child);
child.setParent(this);
}
public void removeChild(TreeNode child) {
getChildren().remove(child);
child.setParent(null);
}
}
</pre>
<h4>
Task.java</h4>
<pre class="brush:java;">package org.uberfire.shared.model;
public class Task extends TreeNode< Folder, TreeNode > {
private String name;
private boolean done;
public Task(String name) {
this.name = name;
this.done = false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
}
</pre>
<h4>
Folder.java</h4>
<pre class="brush:java;">package org.uberfire.shared.model;
public class Folder extends TreeNode< Folder, Task > {
private final String name;
public Folder(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
</pre>
<h4>
Project.java</h4>
<pre class="brush:java;">package org.uberfire.shared.model;
public class Project extends TreeNode< Project, Folder > {
private final String name;
private boolean selected;
public Project(String name) {
// this is the root of the tree so it has no parent
this.name = name;
this.selected = false;
}
public String getName() {
return name;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public int countDoneTasks() {
int doneTasks = 0;
for (Folder folder : getChildren()) {
for (Task task : folder.getChildren()) {
if (task.isDone()) {
++doneTasks;
}
}
}
return doneTasks;
}
public int countTotalTasks() {
int totalTasks = 0;
for (Folder folder : getChildren()) {
totalTasks += folder.getChildren().size();
}
return totalTasks;
}
}
</pre>
<h4>
TasksRoot.java</h4>
<pre class="brush:java;">package org.uberfire.shared.model;
import java.util.ArrayList;
import java.util.List;
public class TasksRoot {
private List< Project > projects = new ArrayList< Project >();
public List< Project > getProjects() {
return projects;
}
}
</pre>
<br />
No surprises here. The only things worth mentioning are:<br />
<br />
<ul>
<li>TreeNode - a generic tree node class which takes PARENT and CHILD type parameters. Note that Project does not have a parent and Task does not have any children and we could have written TreeRootNode, TreeInternalNode and TreeLeafNode classes, but...<a href="https://en.wikipedia.org/wiki/Meh">meh</a>.</li>
<li>TasksRoot - a root object to contain the list of Projects. This will become important during serialization. The instance of this thing is also made available to other beans by way of the @Produces CDI annotation. More about this in Part 2.</li>
<li>Project - this includes two convenience functions for counting the total number of tasks and the number of completed tasks. We'll use this in the Dashboard later on.</li>
</ul>
<div>
Obviously these are some drastic changes here, not to mention the addition of an actual Task model object which was conspicuously missing from the original tutorial. In Part 2 of this blog, I'll describe the changes in the Presenter and View components. Surprisingly, these model changes actually simplified the handling in the "P" and "V" bits so we can concentrate on Uberfire functionality instead of littering the code with "M" bits. Stay tuned...</div>
<div>
<br /></div>
<br />
* <span style="font-family: "helvetica" , "arial" , sans-serif , "verdana"; font-size: 7.33333px; line-height: 60%;">This product is meant for educational purposes only. Any resemblance to real persons, living or dead is purely coincidental. Void where prohibited. Some assembly required. List each check separately by bank number. Batteries not included. Contents may settle during shipment. Use only as directed. No other warranty expressed or implied. Do not use while operating a motor vehicle or heavy equipment. Postage will be paid by addressee. Subject to CAB approval. This is not an offer to sell securities. Apply only to affected area. May be too intense for some viewers. Do not stamp. Use other side for additional listings. For recreational use only. Do not disturb. All models over 18 years of age. If condition persists, consult your physician. No user-serviceable parts inside. Freshest if eaten before date on carton. Subject to change without notice. Times approximate. Simulated picture. No postage necessary if mailed in the United States. Please remain seated until the ride has come to a complete stop. Breaking seal constitutes acceptance of agreement. For off-road use only. As seen on TV. One size fits all. Many suitcases look alike. Contains a substantial amount of non-tobacco ingredients. Colors may, in time, fade. We have sent the forms which seem right for you. Slippery when wet. For office use only. Not affiliated with the American Red Cross. Drop in any mailbox. Edited for television. Keep cool; process promptly. Post office will not deliver without postage. List was current at time of printing. Return to sender, no forwarding order on file, unable to forward. Not responsible for direct, indirect, incidental or consequential damages resulting from any defect, error or failure to perform. At participating locations only. Not the Beatles. Penalty for private use. See label for sequence. Substantial penalty for early withdrawal. Do not write below this line. Falling rock. Lost ticket pays maximum rate. Nap was here. Your canceled check is your receipt. Add toner. Place stamp here. Avoid contact with skin. Sanitized for your protection. Be sure each item is properly endorsed. Sign here without admitting guilt. Slightly higher west of the Mississippi. Employees and their families are not eligible. Beware of dog. Contestants have been briefed on some questions before the show. Limited time offer, call now to ensure prompt delivery. You must be present to win. No passes accepted for this engagement. No purchase necessary. Processed at location stamped in code at top of carton. Shading within a garment may occur. Use only in a well-ventilated area. Keep away from fire or flames. Replace with same type. Approved for veterans. Booths for two or more. Check here if tax deductible. Some equipment shown is optional. Price does not include taxes. No Canadian coins. Not recommended for children. Prerecorded for this time zone. Reproduction strictly prohibited. No solicitors. No alcohol, dogs or horses. No anchovies unless otherwise specified. Restaurant package, not for resale. List at least two alternate dates. First pull up, then pull down. Call toll free number before digging. Driver does not carry cash. Some of the trademarks mentioned in this product appear for identification purposes only. Objects in mirror may be closer than they appear. Record additional transactions on back of previous stub. Unix is a registered trademark of AT&T. Do not fold, spindle or mutilate. No transfers issued until the bus comes to a complete stop. Package sold by weight, not volume. Your mileage may vary. If the flow controls supplied are not installed, this unit will not operate properly. Keep out of reach of children. When this set is used with other equipment, if the picture is not stable or the buzz sound is heard, try to change the mutual position of relevant equipment or take enough distance between them. This unit not labeled for retail sale. Phenylketonurics: contains phenylalanine. Close cover before striking. Mind the gap. No merchantability expressed or implied. Parental discretion is advised. Sold as a novelty item only. Although robust enough for general use, adventures into the esoteric periphery may reveal unexpected quirks. Not available in stores. May cause abdominal cramping and loose stools. Vitamins A, D, E, and K have been added. Not designed or intended for use in on-line control of aircraft, air traffic, aircraft navigation or aircraft communications; or in the design, construction, operation or maintenance of any nuclear facility. Container may explode if heated. May contain traces of various nuts and seeds.</span>Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0tag:blogger.com,1999:blog-4008600950604647427.post-83481352308761513872016-05-18T07:07:00.000-07:002016-05-20T14:23:50.403-07:00Uberfire - The Unanswered Questions: Part 2<h2 style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkz0IkugNZpoZjkhMup-jclmmKeUMjJmZKp-hlDdsYEteK77318BPj5MjQCzASlK2G3V00m3-v8He-bWifOiWvWb971v3vCztMR2wxsQEOSJxPV4D1fPmeD4-CxEpMzstyHTbBllb-Z4g/s1600/presenting2.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkz0IkugNZpoZjkhMup-jclmmKeUMjJmZKp-hlDdsYEteK77318BPj5MjQCzASlK2G3V00m3-v8He-bWifOiWvWb971v3vCztMR2wxsQEOSJxPV4D1fPmeD4-CxEpMzstyHTbBllb-Z4g/s1600/presenting2.png" /></a>And now presenting...</h2>
<div>
As I mentioned in Part 1, my proposed model changes will necessarily cause some changes in the Presenter and View classes. Most of these are as a consequence of removing the Model bits from Presenter and View.</div>
<div>
<br /></div>
<div>
I have made the design decision to make ProjectsPresenter the "owner" of the model root, listening for model change events from the TasksPresenter and making the model changes. This will also be a good place to handle model persistence. More about this later on, but first let's take a look at the new Presenter and View classes. I don't want to bore you by filling this blog with pages of java code, so I'll just present some of the highlights. The complete tutorial can be found on <a href="https://github.com/bbrodt/uberfire-tutorial">github here</a>.</div>
<h2>
</h2>
<h3>
ProjectsPresenter.java
</h3>
<div>
The big changes here are the additions of model change handlers: the @Observes annotations identify the change events which are injected into this class by TaskPresenter. This concept was introduced in the original tutorial, and should already be familiar.<br />
<br />
The model root object, a TasksRoot instance, is made available with the @Produces annotation. This is consumed by the DashboardPresenter class with @Inject. Consequently, the DashboardPresenter code is greatly simplified because it doesn't have to deal with handling events to update its "total tasks" and "tasks done" counters - it can simply ask the model for this information.<br />
<br /></div>
<pre class="brush:java;"> private TasksRoot tasksRoot = new TasksRoot();
@Produces
@Named("tasksRoot")
public TasksRoot tasksRoot() {
return tasksRoot;
}
</pre>
<div>
<br />
The other obvious change is that Project, Folder and Task name strings (which are actually attributes of these model objects) have been replaced by their actual model objects, and the object tree hierarchy is strictly modeled.
</div>
<h3>
TasksPresenter.java</h3>
<div>
Again, the main changes here are replacement of name strings with actual model objects; similar changes were made in the TaskCreated and TaskDone events. These are identical to those in the original tutorial, except for (again) the use of actual model objects instead of strings. I've also added FolderCreated and FolderRemoved events so we can notify the ProjectsPresenter when these are triggered. Recall that the "Create Folder" button is on the TaskView.</div>
<pre class="brush:java;"> @Inject
private Event< TaskCreated > taskCreatedEvent;
@Inject
private Event< TaskDone > taskDoneEvent;
@Inject
private Event< FolderCreated > folderCreatedEvent;
@Inject
private Event< FolderRemoved > folderRemovedEvent;
public void newFolder(String folderName) {
folderCreatedEvent.fire(new FolderCreated(new Folder(folderName)));
updateView();
}
public void removeFolder(Folder folder) {
folderRemovedEvent.fire(new FolderRemoved(folder));
updateView();
}
public void doneTask(Task task) {
taskDoneEvent.fire(new TaskDone(task.getParent(), task));
updateView();
}
public void createTask(Folder folder, Task task) {
taskCreatedEvent.fire(new TaskCreated(folder, task));
updateView();
}
}
</pre>
<div>
<br />
Another thing worth mentioning is that the ProjectsPresenter class notifies TasksPresenter when a different project has been selected. This allows TasksPresenter to redraw its view with content of the new Project.
<br />
<br /></div>
<pre class="brush:java;"> private Project currentSelectedProject;
public void projectSelected(@Observes ProjectSelectedEvent projectSelectedEvent) {
currentSelectedProject = projectSelectedEvent.getProject();
selectFolder();
}
</pre>
<h3>
DashboardPresenter.java
</h3>
<div>
As mentioned previously, the code for this class has been reduced to just a few lines because it no longer listens for task events to update its totals. Instead we use an @Inject to get the instance of the model root object (TasksRoot) and ask it for these totals. The Dashboard view remains the same as before.
<br />
<pre class="brush:java;"> @Inject
@Named("tasksRoot")
private TasksRoot tasksRoot;
private void updateView() {
view.clear();
for (Project project : tasksRoot.getProjects()) {
int done = project.countDoneTasks();
int notDone = project.countTotalTasks() - done;
view.addProject(project, notDone+"", done+"");
}
}
</pre>
<br />
<br /></div>
<h3>
Event Classes
</h3>
<div>
These should be self-explanatory. The TasksPresenter class creates one of these event objects and then fires the event off to any beans that are @Observe'ing these events. Note that the FolderRemoved event is not used yet, but we may want to implement that later.<br />
<br />
<h3>
What's next?</h3>
So far I haven't introduced any new Uberfire concepts, just rearranged some bits to make persistence of the model possible. I'll go into this in Part 3 of this blog.</div>
<div>
<br /></div>
Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0tag:blogger.com,1999:blog-4008600950604647427.post-54801026322896128692016-05-17T11:45:00.000-07:002016-05-22T07:47:49.929-07:00Uberfire - The Unanswered Questions: Part 3<h2>
The Uberfire VFS</h2>
Uberfire supports two types of Virtual File System (VFS) strategies: simple file access using the server host's native file system and a git-based repository file system. As of the current version (0.9.0) of Uberfire, I have only been able to successfully use the git VFS - the simple VFS somehow eludes me. There is also support for client-side file access, and I'll blog about that later.<br />
<br />
Before we can talk about the server-side VFS, we need to take a little detour. A typical Uberfire project is split between server-side and client-side java code. The client code is translated from Java to Javascript by the GWT compiler. There are <a href="https://dzone.com/articles/understanding-gwt-compiler">lots of resources on the web</a> that describe how all of this works, so I won't go into a lot of detail here. Suffice it to say that the *.gwt.xml files in your Uberfire project define what is client-side GWT stuff; everything else is server-side stuff. For example, have a look at the UFTasksShowcase.gwt.xml in the uftasks-webapp project:<br />
<br />
<br />
<pre class="brush:xml;"><?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.4.0//EN"
"http://google-web-toolkit.googlecode.com/svn/tags/2.5.0/distro-source/core/src/gwt-module.dtd">
<module>
<inherits name="org.uberfire.UberfireAPI"/>
<inherits name="org.uberfire.UberfireClientAPI"/>
<inherits name="org.uberfire.UberfireWorkbench"/>
<inherits name="org.uberfire.UberfireJS"/>
<inherits name="org.uberfire.UberfireBackend"/>
<inherits name="org.uberfire.UberfireWorkbenchBackend"/>
<inherits name="org.uberfire.client.views.PatternFly"/>
<inherits name="org.uberfire.component.UFTasksComponentClient"/>
<!-- Specify the paths for translatable code -->
<source path='client'/>
<source path='shared'/>
</module>
</pre>
<br />
This file lives in the "org.uberfire" package in your src/main/resources project folder and defines a GWT "module". The <source path='client'> and <source path='shared'> elements declare that all the classes in the org.uberfire.client and org.uberfire.shared packages and their sub-packages are GWT-managed. Everything else (e.g. the stuff in the org.uberfire.backend package) is server-side.<br />
<br />
The <inherits> elements define the dependencies required by this module. Java source code for these dependencies must be available to the GWT compiler, and must have their own *.gwt.xml resources, i.e. they must also be GWT modules. For example, if you're using the eclipse IDE to build your Uberfire project, you can see these in the "Maven Dependencies" library:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhROcjbh29sQN3HeCCizV5_TH7tnKFUr4wv9DqGMSCtZ7Wot_Kel1MFG31VvFtITdh2lUDBNID0qMPzi8eJVaU9t0r78MFTeNLJcKAsQrpblLXjHXtMttxpP_zRpcSC4s_Z5C4C3VSrUSo/s1600/eclipse-uberfire-gwt-dependencies.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="443" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhROcjbh29sQN3HeCCizV5_TH7tnKFUr4wv9DqGMSCtZ7Wot_Kel1MFG31VvFtITdh2lUDBNID0qMPzi8eJVaU9t0r78MFTeNLJcKAsQrpblLXjHXtMttxpP_zRpcSC4s_Z5C4C3VSrUSo/s640/eclipse-uberfire-gwt-dependencies.png" width="640" /></a></div>
<br />
<h2>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbL7jFfmgmS50XdFsdqhO3od_DJD8e3untdc6A73tO3elri2lQDHDegUxxQx98kEpMS9uVlFfJxPcavMu3tXb2deHACttTRdKajXA221OvKpYp544o2f9GTFBQDjEZVk8AN9lGNKrob-o/s1600/the-producers.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="128" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbL7jFfmgmS50XdFsdqhO3od_DJD8e3untdc6A73tO3elri2lQDHDegUxxQx98kEpMS9uVlFfJxPcavMu3tXb2deHACttTRdKajXA221OvKpYp544o2f9GTFBQDjEZVk8AN9lGNKrob-o/s200/the-producers.png" width="200" /></a></div>
The Producers</h2>
As you have probably already figured out, objects that are @Inject'ed into code must be @Produced from somewhere. In this case, the VFS I/O service instance is produced by a server-side class, named ApplicationScopedProducer in our uftasks-webapp project. The class looks like this:<br />
<br />
<h3>
ApplicationScopedProducer.java</h3>
<pre class="brush:java;">import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import org.uberfire.commons.services.cdi.Startup;
import org.uberfire.commons.services.cdi.StartupType;
import org.uberfire.io.IOService;
import org.uberfire.io.impl.IOServiceNio2WrapperImpl;
@Startup(StartupType.BOOTSTRAP)
@ApplicationScoped
public class ApplicationScopedProducer {
@Inject
private IOWatchServiceAllImpl watchService;
private IOService ioService;
@PostConstruct
public void setup() {
ioService = new IOServiceNio2WrapperImpl("1", watchService);
}
@Produces
@Named("ioStrategy")
public IOService ioService() {
return ioService;
}
}
</pre>
<br />
This class instantiates a default VFS service implementation (IOServiceNio2WrapperImpl) which in this case is a git-based repository, and makes the service available to other application beans. Note the optional use of the @Inject'ed IOWatchServiceAllImpl object. Your application beans can listen for VFS resource changes by @Observe'ing the events broadcast from this service - pretty cool.<br />
<br />
Also notice the @Startup(StartupType.BOOTSTRAP) annotation. This guarantees that this bean will be processed during the container's startup sequence and before any other beans. In other words, the ioService will be made available very early on during the app server's lifecycle. Note that this does not "create" a file system interface yet; this is where the next class comes in.<br />
<h3>
AppSetup.java</h3>
<pre class="brush:java;">package org.uberfire.backend.server;
import java.net.URI;
import java.util.HashMap;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.uberfire.commons.services.cdi.Startup;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.file.FileSystemAlreadyExistsException;
@ApplicationScoped
@Startup
public class AppSetup {
@Inject
@Named("ioStrategy")
private IOService ioService;
@PostConstruct
public void init() {
try {
ioService.newFileSystem( URI.create("default://uftasks"), new HashMap<String, Object>() {{}} );
} catch ( final FileSystemAlreadyExistsException ignore ) {
}
}
}
</pre>
<br />
This class uses the ioService object to create the default file system. This bean is also processed during server startup, but only after all of the other BOOTSTRAP beans have been processed. This file system is registered with the Uberfire VFSService by some voodoo magick, and <b>that's</b> how the client code interacts with the server-side file system. Confused yet? Don't worry, it gets even more confusing. Here's a very simple example of client-side code that reads from a VFS file on the server:<br />
<br />
<pre class="brush:java;"> @Inject
protected Caller<VFSService> vfsServices;
private void readFile(String fileURI) {
vfsServices.call(new RemoteCallback<Path>() {
@Override
public void callback(final Path path) {
vfsServices.call(new RemoteCallback<String>() {
@Override
public void callback(final String response) {
GWT.log("Read Response: " + response);
}
}).readAllString(path);
}
}).get(fileURI);
}
</pre>
<br />
Here, the outer vfsServices.call() returns a Path object by way of the callback() method; the inner call() does the actual file reading from the Path object and its callback() returns the file contents as a String.<br />
<br />
Notice that, because this is an interaction between the client and server, everything is done asynchronously; that is, the vfsServices.call() returns immediately and then, at some point in the future, the server returns its response by way of the callback() method. It's up to your client code to gracefully handle timeouts while waiting for the server to respond.<br />
<br />
There is another way of constructing the Path object without having to go through the vfsServices.call(), and that is to use the PathFactory:<br />
<br />
<pre class="brush:java;">
private final static String DEFAULT_URI = "default://uftasks";
String filename = "tasks.json";
String uri = DEFAULT_URI + "/" + "/" + filename;
Path path = PathFactory.newPath(filename, uri);
</pre>
<br />
and then the file read simply becomes:<br />
<br />
<pre class="brush:java;">
vfsServices.call(new RemoteCallback<Path>() {
@Override
public void callback(final Path response) {
GWT.log("Read Response: " + response);
}
}).readAllString(path);
</pre>
<br/>
File writing is similar in structure:
<br/>
<pre class="brush:java;">
@Inject
protected Caller<VFSService> vfsServices;
private void writeFile(String fileURI, final String content) {
String filename = "tasks.json";
String uri = DEFAULT_URI + "/" + "/" + filename;
Path path = PathFactory.newPath(filename, uri);
vfsServices.call(new RemoteCallback<Path>() {
@Override
public void callback(final Path response) {
GWT.log("Write Response: " + response);
}
}).write(path, content);
}
</pre>
<h2>
Putting it all together</h2>
Now we have pretty much everything we need to be able to serialize our Tasks model. I'll close the loop on this in my next installment, Part 4.
Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com4tag:blogger.com,1999:blog-4008600950604647427.post-557358383367752272016-05-16T11:58:00.000-07:002016-05-23T08:19:44.131-07:00Uberfire - The Unanswered Questions: Part 4<h2>
Dude, where's my file?</h2>
<h3>
Default VFS Root</h3>
In this installment I'll be adding serialization of the Tasks to our project. We will want to be able to look at what's actually being written to the Uberfire VFS, but to do that we need to know where on disk the file system is actually being created.<br />
<br />
By default, the VFS root is in a directory called ".niogit" and is created in the directory from which the web server is started, i.e. the "current directory". Assume that we have installed wildfly (or tomcat, or some other server) in C:/JBoss/wildfly. The server startup scripts are in the "bin" directory, so if we start wildfly like so (Note: I'm using the standard git bash shell here):<br />
<br />
<pre class="brush:shell;">$ cd C:/JBoss/wildfly
$ bin/standalone.sh
</pre>
<br />
In this case the VFS root will be in C:/JBoss/wildfly/.niogit and our git VFS directory will be C:/JBoss/wildfly/.niogit/uftasks.git. Note that this git directory is in a "<a href="http://gitolite.com/detached-head.html">detached head</a>" state. To actually see the files in the working tree we can simply clone the repository:<br />
<br />
<pre class="brush:shell;">$ cd C:/JBoss/wildfly/.niogit
$ git clone uftasks.git
Cloning into 'uftasks'...
done.
</pre>
<br />
This will clone uftasks.git into the directory uftasks, which can then be treated as a normal git repository. Remember to "git pull" in this repository whenever you want to get the latest updates from the VFS. If you make changes to files in this repository, they can be committed and pushed back upstream to the Uberfire VFS.<br />
<h3>
Relocating the VFS Root</h3>
Sometimes it's useful to change the VFS root so that it points to some other directory, or some other drive. This can be done by setting a system property in the server's configuration. See <a href="https://docs.jboss.org/jbpm/v6.0/userguide/wb.Workbench.html#wb.systemProperties">App Server Configuration</a> for more information about how this is done. Essentially, it involves editing an app server configuration file and setting the server property "org.uberfire.nio.git.dir" to point to VFS root.<br />
<br />
During development/testing you can also change the VFS root by adding a Java VM argument to the maven pom. Locate the pom.xml file in the uftasks-webapp project and add the following to the <extraJvmArgs> element:<br />
<br />
<pre class="brush:xml;"> <build>
...
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<configuration>
<extraJvmArgs>-Dorg.uberfire.nio.git.dir=C:/temp -Xmx1536m -XX:CompileThreshold=7000 -Derrai.jboss.home=${errai.jboss.home}</extraJvmArgs>
</pre>
This will relocate the VFS root to C:/temp/.niogit.<br />
<br />
<h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9pZevB4qt0R7re6lGVS1WPunrC0O46mpLRhGAfpAqvZEUO5fWuUgHOYUJS3YGQ1JHnws6Fa4ABkG_wh8Y2KSNuwYrXDzhTEVM0ZgFPUfrCQpA-WskwOcCediMCon5Yw8gmLpJQfkCpPk/s1600/cereal.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9pZevB4qt0R7re6lGVS1WPunrC0O46mpLRhGAfpAqvZEUO5fWuUgHOYUJS3YGQ1JHnws6Fa4ABkG_wh8Y2KSNuwYrXDzhTEVM0ZgFPUfrCQpA-WskwOcCediMCon5Yw8gmLpJQfkCpPk/s1600/cereal.png" /></a>Tasks Serialization</h3>
<div>
We'll be using JSON to serialize the Projects/Folders/Tasks hierarchy and, since ProjectsPresenter is the owner of the "tasksRoot" object, it makes sense to add the reading/writing code to this class. To do this, we need to import the com.google.gwt.json.client package. Note that we can't use just any old JSON library because this code will live on the client side and will be translated from Java to Javascript and the source code needs to available to the GWT compiler. In general, it's safe to assume that any Google GWT packages will be OK to use on the client side.</div>
<div>
<h3>
ProjectsPresenter.java</h3>
</div>
<div>
JSON parsing/serializing is tedious, but relatively straight-forward. Add the following methods to this presenter class:</div>
<br />
<pre class="brush:java;">
@Inject
private User user;
private void saveTasksRoot() {
JSONObject tasksRootJson = new JSONObject();
JSONArray projectsJson = new JSONArray();
int pi = 0;
for (Project p : tasksRoot.getProjects()) {
JSONObject pj = new JSONObject();
pj.put("name", new JSONString(p.getName()));
JSONArray foldersJson = new JSONArray();
int fi = 0;
for (Folder f : p.getChildren()) {
JSONObject fj = new JSONObject();
fj.put("name", new JSONString(f.getName()));
foldersJson.set(fi++, fj);
JSONArray tasksJson = new JSONArray();
int ti = 0;
for (Task t : f.getChildren()) {
JSONObject tj = new JSONObject();
tj.put("name", new JSONString(t.getName()));
tj.put("isDone", JSONBoolean.getInstance(t.isDone()));
tasksJson.set(ti++, tj);
}
fj.put("tasks", tasksJson);
}
pj.put("folders", foldersJson);
projectsJson.set(pi++, pj);
}
tasksRootJson.put("projects", projectsJson);
final String content = tasksRootJson.toString();
String filename = "tasks.json";
String uri = DEFAULT_URI + "/" + user.getIdentifier() + "/" + filename;
Path path = PathFactory.newPath(filename, uri);
vfsServices.call(new RemoteCallback<Path>() {
@Override
public void callback(final Path response) {
GWT.log("Write Response: " + response);
}
}).write(path, content);
}
private void loadTasksRoot() {
String filename = "tasks.json";
String uri = DEFAULT_URI + "/" + user.getIdentifier() + "/" + filename;
Path path = PathFactory.newPath(filename, uri);
vfsServices.call(new RemoteCallback<String>() {
@Override
public void callback(final String response) {
TasksRoot newRoot = new TasksRoot();
JSONObject tasksRootJson = JSONParser.parseStrict(response).isObject();
JSONArray projectsJson = tasksRootJson.get("projects").isArray();
for (int pi=0; pi<projectsJson.size(); ++pi) {
JSONObject pj = projectsJson.get(pi).isObject();
Project p = new Project(pj.get("name").isString().stringValue());
JSONArray foldersJson = pj.get("folders").isArray();
for (int fi=0; fi<foldersJson.size(); ++fi) {
JSONObject fj = foldersJson.get(fi).isObject();
Folder f = new Folder(fj.get("name").isString().stringValue());
JSONArray tasksJson = fj.get("tasks").isArray();
for (int ti=0; ti<tasksJson.size(); ++ti) {
JSONObject tj = tasksJson.get(ti).isObject();
Task t = new Task(tj.get("name").isString().stringValue());
t.setDone(tj.get("isDone").isBoolean().booleanValue());
f.getChildren().add(t);
}
p.getChildren().add(f);
}
newRoot.getProjects().add(p);
}
tasksRoot = newRoot;
updateView();
}
}).readAllString(path);
}
</pre>
<br />
Here we have @Inject'ed the User object, which represents the currently logged-in user. We'll use this user ID as the directory name into which the file "tasks.json" will be written. That ensures that each user will have his/her own task list.<br />
<br />
Next, we want the loadTasksRoot() method to be called as soon as this presenter is loaded. This is done using the @PostConstruct CDI annotation:
<br />
<pre class="brush:java;"> @PostConstruct
public void init() {
loadTasksRoot();
}
</pre>
<br />
Immediately after the JSON file has been read we want to update the Projects view using ProjectsPresenter.updateView(). But, recall that file reading happens asynchronously, which is why the call to updateView() is made within the reader callback() method instead of in our ProjectsPresenter.init() method.
<br />
Finally, we will want to serialize the Tasks tree whenever a change happens in the model. This means we need to add a call to saveTasksRoot() in all of the event handlers, e.g.:<br />
<br />
<pre class="brush:java;"> public void taskCreated(@Observes TaskCreated taskCreated) {
if (activeProject!=null) {
Folder folder = taskCreated.getFolder();
Task task = taskCreated.getTask();
folder.addChild(task);
saveTasksRoot();
updateView();
}
}
</pre>
<br />
That's pretty much all there's to it. When we run this app, we should be able to see the changes on the file system. Let's assume we've relocated the VFS file system to our C:/temp directory (see above.) The GWT JSON serializer is fairly simplistic and just spits out everything on one line, but if you have python installed you can pretty-print the JSON to make it more readable. Run the following commands in a git shell and you should see something like this:
<br />
<pre class="brush:shell;">$ cd C:/temp/.niogit
$ git clone uftasks.git
Cloning into 'uftasks'...
done.
$ cd uftasks
$ git pull
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From C:/temp/.niogit/uftasks
82468cf..0636b2a master -> origin/master
Updating 82468cf..0636b2a
Fast-forward
admin/tasks.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ py -m json.tool <admin/tasks.json
{
"projects": [
{
"name": "Project 1",
"folders": [
{
"name": "Folder 1",
"tasks": [
{
"name": "Task 1",
"isDone": false
},
{
"name": "Task 2",
"isDone": true
}
]
},
{
"name": "Folder 2",
"tasks": [
{
"name": "Task 3",
"isDone": false
},
{
"name": "Task 4",
"isDone": false
}
]
}
]
},
{
"name": "Project 2",
"folders": [
{
"name": "Folder 4",
"tasks": [
{
"name": "Task 7",
"isDone": true
},
{
"name": "Task 8",
"isDone": true
}
]
}
]
}
]
}
</pre>
<h3>
What's next?</h3>
Ideally we don't want to clutter the ProjectsPresenter with a bunch of JSON parsing/serialization code because 1. it doesn't really belong there and 2. the serialization strategy is tightly coupled to the presenter which we want to avoid (what if we decide to use XML instead?)<br />
<br />
In the next installment I'll present a way to decouple serialization from UI code which also makes it possible to do serialization from either the client or server side.Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com4tag:blogger.com,1999:blog-4008600950604647427.post-67045472368334879872016-05-15T08:48:00.000-07:002016-05-25T06:08:52.747-07:00Uberfire - The Unanswered Questions: Part 5<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAx7UMMTByik5YCUCuunD_93zueJZxqa3xbD2GzkF9h-brylle8Qe-SqcYCDi0WShx-gFPpBHrmazUrY5-rl2Zl_gvv4-xJicGObZPZ9TBIi9cy3ayrCNd_pWfBpcm8CqwJUNmPIxMhy8/s1600/service.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="141" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAx7UMMTByik5YCUCuunD_93zueJZxqa3xbD2GzkF9h-brylle8Qe-SqcYCDi0WShx-gFPpBHrmazUrY5-rl2Zl_gvv4-xJicGObZPZ9TBIi9cy3ayrCNd_pWfBpcm8CqwJUNmPIxMhy8/s200/service.png" width="200" /></a></div>
<h2>
Service!</h2>
<div>
In Part 4 I promised to present a better solution to the model serialization problem. In Part 5 I will introduce <a href="https://docs.jboss.org/author/display/ERRAI/Remote+Procedure+Calls+%28RPC%29">Errai RPC (Remote Procedure Calls)</a> and show you a service implementation for our UFTasks tutorial.</div>
<div>
<br /></div>
<div>
Errai supports a very easy to use RPC layer for implementing services that can be called from either the client or server side. This requires three components:</div>
<div>
<ol>
<li>a service interface definition</li>
<li>a service implementation</li>
<li>if model objects will be passed to/from the service, they need to be annotated with @Portable</li>
</ol>
<h3>
Service Interface</h3>
</div>
<div>
The service interface definition is very simple - it just requires a @Remote annotation:</div>
<br />
<pre class="brush:java;">import org.jboss.errai.bus.server.annotations.Remote;
import org.uberfire.component.model.TasksRoot;
@Remote
public interface UFTasksService {
TasksRoot load(String userId);
String save(TasksRoot tasksRoot, String userId);
}
</pre>
<br />
Here we have defined two methods, load and save - their purpose should be obious. Note that since this service will be passing a TasksRoot model object back and forth, the TasksRoot class must be annotated with @Portable (see below).<br />
<h3>
Service Implementation</h3>
<div>
This is our implementation class for the service. Note that Errai already provides a JSON marshalling class - how convenient!</div>
<br />
<pre class="brush:java;">import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.marshalling.client.Marshalling;
import org.uberfire.backend.vfs.Path;
import org.uberfire.backend.vfs.PathFactory;
import org.uberfire.backend.vfs.VFSService;
import org.uberfire.component.model.TasksRoot;
import org.uberfire.component.service.UFTasksService;
@Service
@ApplicationScoped
public class UFTasksServiceImpl implements UFTasksService {
private final static String FILENAME = "tasks.json";
private final static String DEFAULT_URI = "default://uftasks";
@Inject
protected VFSService vfsServices;
@Override
public TasksRoot load(String userId) {
String uri = DEFAULT_URI + "/" + userId + "/" + FILENAME;
Path path = PathFactory.newPath(FILENAME, uri);
String content = vfsServices.readAllString(path);
TasksRoot tasksRoot = Marshalling.fromJSON(content, TasksRoot.class);
return tasksRoot;
}
@Override
public String save(TasksRoot tasksRoot, String userId) {
String content = Marshalling.toJSON(tasksRoot);
String uri = DEFAULT_URI + "/" + userId + "/" + FILENAME;
Path path = PathFactory.newPath(FILENAME, uri);
path = vfsServices.write(path, content);
if (path!=null)
return path.getFileName();
return null;
}
}
</pre>
<br />
Notice here that since this service is being executed on the server side, the VFSService is a "peer" and can be called directly instead of having to use a Callback. Recall that, on the client side the client had to invoke VFSService asynchronously because information was sent over the wire from client to server and then back to client.<br />
<h3>
Model Objects</h3>
<div>
Finally, our model objects need to be annotated with @Portable. This tells Errai that the object will need to be serialized so that it can be sent over the wire between client and server.</div>
<br />
<pre class="brush:java;">import java.util.ArrayList;
import java.util.List;
import org.jboss.errai.common.client.api.annotations.Portable;
@Portable
public class TasksRoot {
private List<project> projects = new ArrayList<project>();
public TasksRoot() {
}
public List<project> getProjects() {
return projects;
}
}
</project></project></project></pre>
<br />
Errai uses Java reflection to traverse the object's class definition. This means that any class fields that are not primitive types, or simple java List types must also be annotated as @Portable - in our case the Project, Folder, Task and TreeNode class definitions.<br />
<br />
Errai does include an <a href="https://docs.jboss.org/author/display/ERRAI/Marshalling">extensive marshalling framework</a>, and we could provide our own custom serialization and deserialization routines instead of relying on Java reflection, but that's a more advanced topic that I may cover later.<br />
<br />
<h3>
Client-side Changes</h3>
Now that we have our UFTasks service, we can use it on the client-side. Recall that the ProjectsPresenter did all of the task loading and saving before. This code now simply becomes:<br />
<br />
<pre class="brush:java;">@ApplicationScoped
@WorkbenchScreen(identifier = "ProjectsPresenter")
public class ProjectsPresenter {
....
@Inject
Caller<UFTasksService> ufTasksService;
....
private void loadTasksRoot() {
ufTasksService.call(new RemoteCallback<TasksRoot>() {
@Override
public void callback(final TasksRoot response) {
if (response!=null)
tasksRoot = response;
else
GWT.log("UFTasksService is unable to load tasks file");
updateView();
}
}).load(user.getIdentifier());
}
private void saveTasksRoot() {
ufTasksService.call(new RemoteCallback<String>() {
@Override
public void callback(final String response) {
GWT.log("Write Response: " + response);
}
}).save(tasksRoot, user.getIdentifier());
}
</pre>
<br />
Everything else stays the same.
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<h2>
Reorganizing UFTasks</h2>
If you have read through the <a href="http://www.uberfireframework.org/docs/">Uberfire documentation</a>, you should already be familiar with the <a href="http://www.uberfireframework.org/docs/tutorial/layoutArchetype.html">layout of the Uberfire Archetype</a>. To summarize, the structure looks something like this:<br />
<ul>
<li><b>bom</b>: "bill of materials" of the archetype. It defines the versions of all the artifacts that will be created in the library</li>
<li><b>parent-with-dependencies</b>: declares all dependencies and versions of the archetype.</li>
<li><b>component</b>: the uberfire component project containing server-side, client-side and common components.</li>
<li><b>showcase</b>: uberfire showcase directory containing the web app and distribution-wars.</li>
</ul>
<div>
Following this archetype, it's probably a good idea to reorganize some of our classes. Here's the new project structure:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvinok69bWAateuZn_Iv-J10qBegoRGcb0xEkbb4ZybvWOF-vheftbijuGYjqJky1hTai2061TaIzBXLYcJF7utgUNO2yVhTTofnXdLOTOSJMQHe-pR97D7JeCzMVsMaXxhjcyuVyvURc/s1600/restructuring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvinok69bWAateuZn_Iv-J10qBegoRGcb0xEkbb4ZybvWOF-vheftbijuGYjqJky1hTai2061TaIzBXLYcJF7utgUNO2yVhTTofnXdLOTOSJMQHe-pR97D7JeCzMVsMaXxhjcyuVyvURc/s1600/restructuring.png" /></a></div>
<div>
<br /></div>
<div>
Notice that the model objects have been moved to the components project since they are now considered "shared" between client and server. The same applies to the UFTasksService interface, since this is used by both client and server code. The UFTasksServiceImpl implementation however now resides on the server side.</div>
<div>
<br /></div>
<div>
If you are using Eclipse, this is simply a refactoring operation and all references to these classes should be updated automatically. If you get confused you can always <a href="https://github.com/bbrodt/uberfire-tutorial.git">grab the latest code from here</a>.<br />
<h2>
A Closer Look at Errai Marshalling</h2>
The first thing you'll notice when running this version of UFTasks is that the original JSON file developed in Part 4 is no longer compatible with this version of the app. That's because, as I mentioned earlier, Errai uses Java reflection to figure out the structure of the model object being serialized. What you'll see after creating a new uftasks.json file is something like this:<br />
<br />
<pre class="brush:shell;">{
"^EncodedType": "org.uberfire.component.model.TasksRoot",
"^ObjectID": "1",
"projects": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "2",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Project",
"^ObjectID": "3",
"name": "p1",
"selected": true,
"parent": null,
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "4",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Folder",
"^ObjectID": "5",
"name": "f1",
"parent": {
"^EncodedType": "org.uberfire.component.model.Project",
"^ObjectID": "3"
},
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "6",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Task",
"^ObjectID": "7",
"name": "t1",
"done": false,
"parent": {
"^EncodedType": "org.uberfire.component.model.Folder",
"^ObjectID": "5"
},
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "8",
"^Value": []
}
}
]
}
}
]
}
}
]
}
}
</pre>
<br />
<div>
Notice that Errai inserts a bunch of metadata into the JSON. This is required so that the marshaller knows exactly how to reconstruct the model when parsing this JSON.<br />
<br /></div>
<h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSmT0Wu42mqoDymdSDZrO7Q_8VVCwSO789ptaC7TaPvCbkba9jRDn0KWNiDab3gQgg4rZXx2fls-OOEmVs4VKwLtqPhoGXZOQS1t7WbByLjuoqaIvPB7p8Hxsx_5hSkbES-KWsOKNjUJg/s1600/are-we-there-yet.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSmT0Wu42mqoDymdSDZrO7Q_8VVCwSO789ptaC7TaPvCbkba9jRDn0KWNiDab3gQgg4rZXx2fls-OOEmVs4VKwLtqPhoGXZOQS1t7WbByLjuoqaIvPB7p8Hxsx_5hSkbES-KWsOKNjUJg/s200/are-we-there-yet.png" width="200" /></a>Are we there yet? Are we there yet? Are we...</h2>
<div>
Don't make me turn this blog around and go back home! Of course we aren't there yet! So, what's next? In the next installment I plan to explore the differences between WorkbenchPartViews and WorkbenchEditors. One could argue that everything a WorkbenchEditor does can also be done by a WorkbenchPartView, but <a href="https://www.youtube.com/watch?v=nC9OJb9bJh4">who am I to blow against the wind</a>. Stay tuned.</div>
<div>
<br /></div>
</div>
Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0tag:blogger.com,1999:blog-4008600950604647427.post-13983235374658494412016-05-14T14:59:00.000-07:002016-06-03T14:19:09.627-07:00Uberfire - The Unanswered Questions: Part 6<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPfyDJXK-yNDR__73dFm25d4tSRDQmsJTL6bSr5vLp62NcLidm4MtcI1IT5DmLg0u6hX7UUnGA3DXNnY-2sBS86hjvY7_f46iXwI8UAMxcQuZ9l4peDB8quSAP08-QsX-KsuO0OL85Ae0/s1600/checkmark.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPfyDJXK-yNDR__73dFm25d4tSRDQmsJTL6bSr5vLp62NcLidm4MtcI1IT5DmLg0u6hX7UUnGA3DXNnY-2sBS86hjvY7_f46iXwI8UAMxcQuZ9l4peDB8quSAP08-QsX-KsuO0OL85Ae0/s200/checkmark.png" width="100" /></a></div>
<h2>
TO-DO: Make a TO-DO List</h2>
In this session I'll be creating an editor for the TO-DO Task object. I'll start off by duplicating the Presenter-View pattern already established by the other two popups for NewFolder and NewProject. The reason for this is because I want to explore some new GWT widgets for my Task Editor and I don't want to complicate things by adding all of the other junk required to support a WorkbenchEditor. In this round we'll just create a modal popup dialog to host the Task Editor but eventually we may want to place it somewhere in the workbench window.<br />
<br />
This first step is <a href="http://www.urbandictionary.com/define.php?term=dead%20simple">dead simple</a>: copy and rename the three files named NewFolder<something> in the <b>org.uberfire.client.screens.popup</b> package and replace "NewFolder" with "TaskEditor". So, you should have TaskEditorPresenter,java TaskEditorView.java and TaskEditorView.html and they should look something like this:<br />
<h3>
TaskEditorPresenter.java</h3>
<pre class="brush:java;">@Dependent
public class TaskEditorPresenter {
public interface View extends UberView<TaskEditorPresenter> {
void show();
void hide();
}
@Inject
private View view;
private TasksPresenter tasksPresenter;
@PostConstruct
public void setup() {
view.init( this );
}
public void show( TasksPresenter tasksPresenter ) {
this.tasksPresenter = tasksPresenter;
view.show();
}
public void close() {
view.hide();
}
}
</pre>
<h3>
TaskEditorView.java</h3>
<pre class="brush:java;">@Dependent
@Templated
public class TaskEditorView extends Composite
implements TaskEditorPresenter.View {
private TaskEditorPresenter presenter;
private Modal modal;
@Inject
@DataField("ok-button")
Button okButton;
@Inject
@DataField("cancel-button")
Button cancelButton;
@Override
public void init( TaskEditorPresenter presenter ) {
this.presenter = presenter;
this.modal = new Modal();
final ModalBody body = new ModalBody();
body.add( this );
modal.add( body );
}
@Override
public void show() {
modal.show();
}
@Override
public void hide() {
modal.hide();
}
@EventHandler("ok-button")
public void onOk( ClickEvent event ) {
presenter.close();
}
@EventHandler("cancel-button")
public void onCancel( ClickEvent event ) {
presenter.close();
}
}
</pre>
<h3>
TaskEditorView.html</h3>
<pre class="brush:html;"><div>
<form data-field="task-editor-modal">
<fieldset>
<legend>Task Editor</legend>
<div class="form-group">
</div>
</fieldset>
</form>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-field="cancel-button">Cancel</button>
<button type="button" class="btn btn-primary" data-field="ok-button">OK</button>
</div>
</div>
</pre>
<br />
Then in TasksPresenter.java add this:
<br />
<br />
<pre class="brush:java;">import org.uberfire.client.screens.popup.TaskEditorPresenter;
@Inject
private TaskEditorPresenter taskEditorPresenter;
public void showTaskEditor(Task task) {
taskEditorPresenter.show(this);
}
</pre>
<br />
and in TasksView.java change the generateTasks() method to this:
<br />
<br />
<pre class="brush:java;"> private ListGroupItem generateTask(Task task) {
TaskItem taskItem = new TaskItem(task);
taskItem.add(createTaskCheckbox(task));
taskItem.add(createTaskNotesButton(task));
return taskItem;
}
</pre>
<br />
This will append a button with an EDIT icon to the name in the task list. Finally add the createTasksNotesButton method:
<br />
<br />
<pre class="brush:java;">import org.gwtbootstrap3.client.ui.constants.IconType;
private Button createTaskNotesButton(Task task) {
Button button = GWT.create(Button.class);
button.setIcon(IconType.EDIT);
button.setMarginLeft(20.0);
button.addClickHandler(event -> presenter.showTaskEditor(task));
return button;
}
</pre>
<br />
Rebuild the whole mess and run it - you should that the tasks in the list now have an EDIT icon next to them.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpLzr1n6gcCHQdflnnOgkreFj6zVQlnGeLpZEjwhB97Y5MYYyze7SQaSG67040Ncef1VpubVpSNKSC26eXM9RNEqu19tcvqlUk8H1IOfbdRpxkWPkP-XDgpG_NJ-n_8lSk2gUR5F1wxgU/s1600/TaskListWithButtons.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="377" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpLzr1n6gcCHQdflnnOgkreFj6zVQlnGeLpZEjwhB97Y5MYYyze7SQaSG67040Ncef1VpubVpSNKSC26eXM9RNEqu19tcvqlUk8H1IOfbdRpxkWPkP-XDgpG_NJ-n_8lSk2gUR5F1wxgU/s640/TaskListWithButtons.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="" style="clear: both; text-align: left;">
When you click that icon you'll just get an empty modal popup dialog with an "OK" and "Cancel" button. And now, it's time for...</div>
<div class="" style="clear: both; text-align: left;">
<br /></div>
<h2 style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwLEwZkuH2my08D77nfWIFmnNgrelf-I5ppnnu-kY4Lc7-S_b_I-v61SIlcnEFwcTvahOIju9wmXS5ArSXGos0UqIIrS4ESOG2_xuEFSpsf0XIuTC6YRXjfuSlSAoz7uXhwFcqWuWfwN8/s1600/dilbert-widget.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwLEwZkuH2my08D77nfWIFmnNgrelf-I5ppnnu-kY4Lc7-S_b_I-v61SIlcnEFwcTvahOIju9wmXS5ArSXGos0UqIIrS4ESOG2_xuEFSpsf0XIuTC6YRXjfuSlSAoz7uXhwFcqWuWfwN8/s200/dilbert-widget.png" width="200" /></a>Fun With Widgets!</h2>
<div>
At the beginning of this series I mentioned that a Task should have more than just a brief description (the "name" attribute) mostly because I needed an excuse to build on an already freaking awesome web app. It would be nice to be able to add some notes, possibly in the form of a Rich Text document with 4x8 color glossy pictures and circles and arrows and such. I also need to be able to prioritize my TO-DO list, so let's assign a numeric priority value to each Task. Finally, I would like to know when a Task needs to be completed, so each Task needs a due date.</div>
<div>
<br /></div>
<div>
If all this sounds a bit contrived, that's because it is - it's an excuse to have some <b>Fun With Widgets!</b></div>
<h3>
Rich Text</h3>
<div>
The GWT Rich Text Area is a text editor that allows complex styling and formatting. This widget is browser dependent and may not work as expected for all browsers.<br />
<br />
We'll use this for our Task notes field, so let's go ahead and add one of these to our popup. First, add an anchor to TaskEditorView.html:
<br />
<br /></div>
<pre class="brush:html;"> <form data-field="task-editor-modal">
<fieldset>
<legend>Task Editor</legend>
<div class="form-group">
<a data-field="task-notes"></a>
</div>
</fieldset>
</form>
</pre>
<br />
Then create the Rich Text Area in TaskEditorView.java and attach it to the anchor:
<br />
<br />
<pre class="brush:java;"> RichTextArea notesTextArea;
@Inject
@DataField("task-notes")
Anchor taskNotesAnchor;
@Override
public void init( TaskEditorPresenter presenter ) {
...
// add the other widgets
notesTextArea = new RichTextArea();
notesTextArea.setHeight("200");
notesTextArea.setWidth("100%");
notesTextArea.setHTML("<b>Hello World!</b><br/>Uberfire is cool");
taskNotesAnchor.add(notesTextArea);
}
</pre>
<br />
<h3>
List Box (a.k.a. "ComboBox")</h3>
This is general-purpose selection list which can either be the drop-down "Combo Box" style, or simply a list of selections. We're going to use this for our Task priority field. Again, in the html template, add an anchor for our ListBox widget:<br />
<br />
<br />
<pre class="brush:html;"> <form data-field="task-editor-modal">
<fieldset>
<legend>Task Editor</legend>
<div class="form-group">
<a data-field="task-notes"></a>
<b>Priority:</b> <a data-field="task-priority"></a>
</div>
</fieldset>
</form>
</pre>
<br />
And in the View class:<br />
<br />
<pre class="brush:java;"> @Inject
@DataField("task-priority")
Anchor taskPriorityAnchor;
ListBox priorityListBox;
@Override
public void init( TaskEditorPresenter presenter ) {
...
priorityListBox = new ListBox();
priorityListBox.setMultipleSelect(false);
priorityListBox.addItem("Medium", "2");
priorityListBox.addItem("High", "3");
priorityListBox.addItem("Low", "1");
priorityListBox.addItem("Screw it, I don't time for this crap!", "0");
taskPriorityAnchor.add(priorityListBox);
}
</pre>
<br />
<h3>
Date Picker</h3>
Finally, let's add a Date Picker widget for the Task due date. In the html template add an anchor for the widget:
<br />
<br />
<pre class="brush:html;"> <form data-field="task-editor-modal">
<fieldset>
<legend>Task Editor</legend>
<div class="form-group">
<a data-field="task-notes"></a>
<b>Due Date:</b> <label data-field="task-due-date-label">Due Date</label> <a data-field="task-due-date"></a>
<b>Priority:</b> <a data-field="task-priority"></a>
</div>
</fieldset>
</form>
</pre>
<br />
And add the relevant bits in the View class:<br />
<br />
<pre class="brush:java;"> public static class DueDateValueChangeHandler implements ValueChangeHandler<Date> {
private final Label text;
public DueDateValueChangeHandler(Label text) {
this.text = text;
}
public void onValueChange(ValueChangeEvent<Date> event) {
Date date = event.getValue();
String dateString = DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM).format(date);
text.setText(dateString);
}
}
@Inject
@DataField("task-due-date")
Anchor taskDueDateAnchor;
@Inject
@DataField("task-due-date-label")
Label dueDateLabel;
DatePicker dueDatePicker;
@Override
public void init( TaskEditorPresenter presenter ) {
...
dueDatePicker = new DatePicker();
dueDatePicker.setYearArrowsVisible(true);
dueDatePicker.setYearAndMonthDropdownVisible(true);
dueDatePicker.setVisibleYearCount(51);
dueDatePicker.addValueChangeHandler(new DueDateValueChangeHandler(dueDateLabel));
dueDatePicker.setValue(new Date(), true);
taskDueDateAnchor.add(dueDatePicker);
}
</pre>
<br />
Note the internal class DueDateValueChangeHandler. This is called when the user selects a date from the DatePicker and updates the Due Date label in the html template.<br />
<br />
<h2>
Stylings</h2>
The default DatePicker presentation is pretty lame so I've added a stylesheet to make it look a bit better. A note about CSS or stylesheets: If you create a ".css" file with the same name as your html template file, Errai will automatically load that file and apply the styles to the widgets in your template. In this case, I have defined some styles for the gwt-DatePicker in a file named TaskEditorView.css.<br />
<br />
You can also specify a stylesheet using @Templated(stylesheet=“some/other/path/stylesheet.css”) in your Viewer java class.<br />
<br />
Also, the usual stylesheet inclusion methods still work in Errai: any styles or CSS files included in your src/main/webapp/index.html or src/main/webapp/login.jsp will be available to the entire application.<br />
<br />
I'll save you the boring details of TaskEditorView.css here; instead grab the <a href="https://github.com/bbrodt/uberfire-tutorial/tree/checkpoint-8">latest tag from my repository</a> and enjoy.<br />
<br />
<h2>
Let 'er Rip!</h2>
Let's build and run this sucker and see what happens. If everything went right, you should see this popup when you click on one of the Task EDIT buttons:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAw1Vlyy3INJzX0orZUmfcjFjwxWEJ79rgrqiII5nB2RVWnM6BjM53jN45jKJJJ60BP9-_UrVNdfaZBERiMS9SvfGr-V04LOS7yZ2mE6i3sKp3Rc7E9Ih0y5EjwfsyVuhTgxII5GLaEJw/s1600/TaskEditorPopup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAw1Vlyy3INJzX0orZUmfcjFjwxWEJ79rgrqiII5nB2RVWnM6BjM53jN45jKJJJ60BP9-_UrVNdfaZBERiMS9SvfGr-V04LOS7yZ2mE6i3sKp3Rc7E9Ih0y5EjwfsyVuhTgxII5GLaEJw/s640/TaskEditorPopup.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<h3 style="clear: both; text-align: left;">
</h3>
<h2 style="clear: both; text-align: left;">
<br /></h2>
<h2 style="clear: both; text-align: left;">
What About WorkbenchEditor?</h2>
<div class="" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwQVurJH6fFtEtoD-Cv_aMZC4Xl8LW4qAlGUpRp5bYNbaIK9xiUQ6L-9GU_kEk4jh0z3h5IvvGH8N2dIJyeNlfuASPr_-3K9fZnEQxSaODli5cpECx_RTZdkrTyYGVMgxwIXaIYy0V2Rs/s1600/cake.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwQVurJH6fFtEtoD-Cv_aMZC4Xl8LW4qAlGUpRp5bYNbaIK9xiUQ6L-9GU_kEk4jh0z3h5IvvGH8N2dIJyeNlfuASPr_-3K9fZnEQxSaODli5cpECx_RTZdkrTyYGVMgxwIXaIYy0V2Rs/s200/cake.jpg" width="200" /></a>I know that last time I promised to dive deeper into the inner workings of a WorkbenchEditor, but I think it was important to explore the relationship between the html template and view and have some <b>Fun With Widgets! </b>I promise to show you how WorkbenchEditor works in Part 7 of this series.</div>
Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com3tag:blogger.com,1999:blog-4008600950604647427.post-35529940657190660842016-05-13T10:12:00.000-07:002016-06-20T19:10:51.251-07:00Uberfire - The Unanswered Questions: Part 7<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBuVFF6gAuRgk-JatNSbb_fkhcCcBIf_xIAoa9gKXz_blGQ57byxQupEnatiRfjBNXVgtNgQCyMZGRE4amvjPvB2zgD97y5PCvhLGli8q2ulMCgDtUdYa-Y54Nq-ZC4oo8Gbn2QZHl60c/s1600/GoodNewsEveryone.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBuVFF6gAuRgk-JatNSbb_fkhcCcBIf_xIAoa9gKXz_blGQ57byxQupEnatiRfjBNXVgtNgQCyMZGRE4amvjPvB2zgD97y5PCvhLGli8q2ulMCgDtUdYa-Y54Nq-ZC4oo8Gbn2QZHl60c/s200/GoodNewsEveryone.png" width="200" /></a></div>
<h2>
Good News Everyone!</h2>
...is what I would have said if there had been good news. The news is that, as of June 8, Uberfire has migrated from wildfly 8.1 to wildfly 10 and GWT's SuperDevMode will no longer work with 8.1. Well, it actually <b>is</b> good news because, considering version 10 has been around for more than a year, Uberfire has finally caught up. The "<a href="https://www.youtube.com/watch?v=YRCzEqkCoiM">not so good news, everyone</a>" is that we'll have to make some minor changes to our maven pom files to use the new wildfly version so we can use SuperDevMode to debug our web app.<br />
<br />
<br />
<h2>
This Week's Blog</h2>
<div>
In this installment I will finally introduce Uberfire WorkbenchEditors, and explain how they differ from Screens. This will require some additional GWT and Uberfire components that were not included in previous versions of our UFTasks tutorial, which means changes to some of the build files.<br />
<br />
I'll also introduce some new concepts that are unique to editors, and I'll point out some restrictions imposed by the Uberfire framework for launching and passing information to/from editors. Let's get started...</div>
<br />
<br />
<div class="vk_ans" style="font-family: arial, sans-serif-light, sans-serif; font-size: xx-large !important; font-weight: lighter !important; margin-bottom: 0px;">
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2fV8wH-SUp0EloAzme_cmEvqb_1vbRAPb9UJcXRWhXGGrp4mcOoMNa2YlQ1eiiuMdDQxiSrkurWJCHmniTymLGrqQx_hZ1FSbcoZVY-3xEA41eEJQ6uDv9uh0hjrI09VR-DdDstv6XH4/s1600/pom.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2fV8wH-SUp0EloAzme_cmEvqb_1vbRAPb9UJcXRWhXGGrp4mcOoMNa2YlQ1eiiuMdDQxiSrkurWJCHmniTymLGrqQx_hZ1FSbcoZVY-3xEA41eEJQ6uDv9uh0hjrI09VR-DdDstv6XH4/s1600/pom.png" /></a><span data-dobid="hdw">pom </span><span style="font-family: "arial" , sans-serif; font-size: medium; font-weight: lighter;">/</span><span style="font-family: "arial" , sans-serif; font-size: medium; font-weight: lighter;">ˈpäm/</span><br />
<div style="font-family: arial, sans-serif; font-size: small;">
<div>
<div class="lr_dct_sf_h" style="padding-top: 10px;">
<i>noun</i></div>
<div aria-hidden="true" class="xpdxpnd vk_gy" data-mh="-1" style="color: rgb(135, 135, 135) !important; max-height: 0px; overflow: hidden; transition: max-height 0.3s;">
<b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b></div>
one half of a pom-pom</div>
</div>
</div>
<div>
<br />
<br />
<br />
<br />
<br />
First let's address the wildfly 10 change: in the pom.xml file located in the uftasks-webapp directory, look for the <as.version> property at the top of the file and change its value to 10.0.0.Final. So, you should have this:</div>
<br />
<pre class="brush:xml;"> <as.version>10.0.0.Final</as.version>
</pre>
<br />
As long as we're mucking around in here, let's also add the necessary dependencies to support our WorkbenchEditor; add the following dependencies to the pom.xml:<br />
<br />
<pre class="brush:xml;"> <dependency>
<groupId>org.uberfire</groupId>
<artifactId>uberfire-workbench-processors</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.uberfire</groupId>
<artifactId>uberfire-commons-editor-client</artifactId>
</dependency>
<dependency>
<groupId>org.uberfire</groupId>
<artifactId>uberfire-commons-editor-api</artifactId>
</dependency>
<dependency>
<groupId>org.uberfire</groupId>
<artifactId>uberfire-widgets-commons</artifactId>
</dependency>
<dependency>
<groupId>org.uberfire</groupId>
<artifactId>uberfire-widgets-core-client</artifactId>
</dependency>
</pre>
<br />
and in the <compileSourcesArtifacts> section, add these lines:<br />
<br />
<pre class="brush:xml;"> <compileSourcesArtifact>org.uberfire:uberfire-commons-editor-api</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-commons-editor-client</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-core-client</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-commons</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-table</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-properties-editor-api</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-properties-editor-client</compileSourcesArtifact>
<compileSourcesArtifact>org.uberfire:uberfire-widgets-service-api</compileSourcesArtifact>
</pre>
That should be all that is needed; rebuild the entire application and test to make sure that it still works, and that it now downloads and installs wildfly 10.<br />
<br />
<h2>
Place Manager and Place Requests</h2>
<div>
The original UFTasks tutorial briefly mentioned the Place Manager and the concept of "places". We saw this service used in the ShowcaseEntryPoint class of our application to open different Perspectives. The Place Manager is used in general to open any kind of "managed window", and tracks which are currently active. The term "managed window" refers to any one of a number of different types of windows decorated with one of the annotations defined in the org.uberfire.client.annotations package; some of these you already know, e.g. WorkbenchScreen, WorkbenchEditor and Perspective. There are other types of managed windows, which I'll discuss in future blogs, but for now let's concentrate on the ones we know about.<br />
<br />
The Place Manager keeps an internal lookup table of all currently open managed windows. The lookup key varies, depending on the type of window.</div>
<div>
<br /></div>
<div>
When your application wants to open a Screen, Perspective, or an Editor, it needs to construct a PlaceRequest. For Screens (and Perspectives and certain other types of managed windows), the PlaceRequest is simple: it only needs the Screen identifier, declared in the @WorkbenchScreen annotation as in, for example:</div>
<br />
<pre class="brush:java;">@WorkbenchScreen(identifier = "ProjectsPresenter")
</pre>
<br />
This is because your application can only have one instance of a Screen type open at a time. The PlaceRequest is then simply:
<br />
<br />
<pre class="brush:java;">PlaceRequest pr = new DefaultPlaceRequest("ProjectsPresenter");
</pre>
<br />
<div>
Workbench Editors, however can have multiple instances of the same type open at the same time, so the Place Request uses the file Path being edited as the lookup table key. In this case we need to use a PathPlaceRequest, like so:</div>
<br />
<pre class="brush:java;">Path path = getFilePathToEdit();
PlaceRequest pr = new PathPlaceRequest(path);
</pre>
<div>
<br /></div>
<div>
We'll see how this works later.<br />
<br /></div>
<h2>
The Workbench Editor</h2>
A WorkbenchEditor is constructed the same as any of our other Presenter classes, except it uses the @WorkbenchEditor annotation:<br />
<br />
@WorkbenchEditor(identifier = "TaskEditor", supportedTypes = { TaskResourceType.class })<br />
<br />
The "supportedTypes" attribute tells the Place Manager the file types (actually the filename extensions) to associate with this editor.<br />
<br />
An Editor also needs to provide some additional UI and lifecycle bits, which are identified by annotations. These are:<br />
<br />
<ul>
<li>@PostConstruct - method that is called immediately after construction of the editor Presenter class to initialize the editor's View class</li>
<li>@OnStartup - startup method that is called after the @PostConstruct method, which is responsible for initializing internal data structures and loading the editor content</li>
<li>@WorkbenchPartTitle - a method that returns the title text for the editor</li>
<li>@WorkbenchPartTitleDecoration - optional method that returns the editor title widget</li>
<li>@WorkbenchPartView - method that returns the View for the editor</li>
<li>@WorkbenchMenu - method that returns an editor-specific menu, which may be null</li>
</ul>
<br />
I won't go into the excruciatingly boring details of the implementations of each of these methods. As always, the current code can be downloaded from the git repository.<br />
<h3>
Resource Types</h3>
<div>
A Resource Type simply identifies the content of a file, similar in concept to the <a href="http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fruntime_content_contributing.htm">Eclipse Content Types</a>, except that Uberfire associates only the file extension with the Resource Type - there is no "peeking" inside the file to determine what's in it.</div>
<div>
<br /></div>
<div>
When defining a Workbench Editor, you must provide one or more Resource Types that the editor can handle. For example, a text editor may declare that it can be used for both a Text Resource Type and an XML Resource Type. The editor may also declare a "priority" to help Uberfire determine the best editor to use for a particular Resource Type. Thus, if your application contains both an advanced XML editor with syntax highlighting and tag completion and a bunch of other cool stuff, along with a plain text editor, Uberfire will choose the XML editor if it declares a higher priority in the @WorkbenchEditor annotation.</div>
<div>
<br /></div>
<div>
For our Task Editor we will create a new ResourceType to handle Task objects. The ResourceType definition looks like this:</div>
<br />
<pre class="brush:java;">@ApplicationScoped
public class TaskResourceType implements ClientResourceType {
@Override
public String getShortName() {
return "task";
}
@Override
public String getDescription() {
return "TO-DO Task file";
}
@Override
public String getPrefix() {
return "";
}
@Override
public String getSuffix() {
return "task";
}
@Override
public int getPriority() {
return 0;
}
@Override
public String getSimpleWildcardPattern() {
return "*.task";
}
@Override
public boolean accept(Path path) {
return path.getFileName().endsWith( "." + getSuffix() );
}
@Override
public IsWidget getIcon() {
return null;
}
}
</pre>
<br />
This is pretty straightforward: it simply defines a file extension of "task" and provides some descriptive text and an icon for use with, for example, a file browser screen if one were provided. Now when we ask the Place Manager to open a VFS file with a ".task" extension, it will know to instantiate our Task Editor.<br />
<h2>
</h2>
<h3>
The View</h3>
<div>
Surprisingly, the Task editor view has not changed very much from <a href="https://github.com/bbrodt/uberfire-tutorial/blob/checkpoint-8/uftasks/uftasks-showcase/uftasks-webapp/src/main/java/org/uberfire/client/screens/popup/TaskEditorView.java">its previous incarnation</a>, which was hosted in a popup modal dialog box. Obviously the show() and hide() methods, which controlled the visibility of the modal dialog, have gone away. The TaskEditorView interface now looks like this (recall that this interface is defined internally in TaskEditorPresenter):
</div>
<br />
<pre class="brush:java;"> public interface View extends UberView<TaskEditorPresenter> {
IsWidget getTitleWidget();
void setContent(final TaskWithNotes content);
TaskWithNotes getContent();
boolean isDirty();
}
</pre>
<br />
The getTitleWidget() method satisfies the @WorkbenchPartTitleDecoration requirement for the Presenter. The setContent() and getContent() methods deserve some explanation (see Argh! More Model Changes?) and the isDirty() method is used by the Presenter to determine if the Task object has changed and needs to be persisted.
<br />
<br />
<h2>
Firing up the Editor</h2>
In the previous incantation of this code, we opened the modal dialog popup used to edit the Task, from the TasksPresenter. Recall that the associated View had an "edit" button for each task which, when clicked, caused the dialog to show. We'll still keep this method for opening the Task Editor, but instead we'll use a Place Manager request to open our Workbench Editor. Our showTaskEditor() method in TasksPresenter now looks like this:<br />
<br />
<pre class="brush:java;"> public void showTaskEditor(final Task task) {
this.task = task;
ufTasksService.call(new RemoteCallback<String>() {
@Override
public void callback(final String response) {
if (response!=null) {
String filename = response.replaceFirst(".*/", "");
Path path = PathFactory.newPath(filename, response);
placeRequest = new PathPlaceRequest(path);
placeManager.goTo(placeRequest);
}
else
GWT.log("UFTasksService is unable to load tasks file");
}
}).getFilePath(user.getIdentifier(), task);
}
public PathPlaceRequest getPlaceRequest() {
return placeRequest;
}
public Task getTask() {
return task;
}
</pre>
<br/>
Here, we have added a new method to the UFTasksService called getFilePath(). This returns a file name string for the task notes file (the "*.task" ResourceType.) Since the UFTasksService already knows the file system location of the Tasks list file ("tasks.json") it seemed like a logical place to provide the task notes file name as well. The task notes file name is simply a concatenation of the User ID, Task ID and the ".task" file name suffix.<br />
<br />
Notice that the placeRequest object is a TasksPresenter class field, which is provided to the TaskEditorPresenter by way of getPlaceRequest(). This is required so that the Editor can properly close the window when requested by the user. Apparently Place Manager requires the same PlaceRequest object that opened an editor, to be used to close it as well. This is because it uses the hashcode of the PlaceRequest object as the key in its internal lookup table. It beats the heck outta me why this is so, but...there it is. Maybe one of the Uberfire gurus can explain why it's done this way instead of using the PlaceRequest identifier and/or Path value. This brings me to my next topic:<br />
<h3>
Parameter Passing</h3>
Place Manager only allows String name/value pairs to be passed into an Editor. This can be done either in one of the PlaceRequest constructors (DefaultPlaceRequest(identifier, Map<String,String> parameters)) or using the addParameter() method after construction. This means that if you want to pass an object into your Editor, you'll have to serialize the object as String name/value pairs, or as JSON - bummer :(<br />
<br />
Notice that I chose to provide the Task object being edited by way of TasksPresenter#getTask() instead of serializing it and passing it through the Place Request parameters map. If there's a better way of doing this, I'd be interested to find out.<br />
<br />
<h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-FrxGl4EzXqeXe3ZzD63sinytYcp8uKIIRz4EiRPQBdXojKNtv41ZLLsUJtj_sSLpkEZYlCFyeChmPXcJ9kew3eJ7bHtOSN6pHMHuePFX0afj-hXBB-Uqv9Zh0hm6VxLZxQVknsHuFZQ/s1600/NoMoreChanges.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-FrxGl4EzXqeXe3ZzD63sinytYcp8uKIIRz4EiRPQBdXojKNtv41ZLLsUJtj_sSLpkEZYlCFyeChmPXcJ9kew3eJ7bHtOSN6pHMHuePFX0afj-hXBB-Uqv9Zh0hm6VxLZxQVknsHuFZQ/s200/NoMoreChanges.png" width="200" /></a>Argh! More Model Changes?</h2>
<div>
Oh what, you thought we were done hacking around in the model? First, we haven't even defined the additional bits of information that we wanted to include in a Task object (priority, due date, etc.) and second, the Presenter-Model interaction will require some additional support from the Model.</div>
<div>
<br /></div>
<div>
Besides the aforementioned bits, we also want the Task to have an arbitrarily long Rich Text "notes" attribute which the user can view and update inside our Task Editor, but we don't want to incur the penalty of having to marshal this potentially large amount of data across the wire for every task displayed by the TasksPresenter/TasksView list. Instead we only want to load the notes when the user opens the Task Editor. This is accomplished using a separate text file associated with a Task instance. The text file name is constructed using the Task's "id" field suffixed with a ".task" file extension. Thus, when we issue a Place Request with this file name, Place Manager will locate and open our Editor.<br />
<br />
Our new Task object now contains the following fields (along with getters and setters):<br />
<br />
<pre class="brush:java;">public class Task extends TreeNode<Folder, TreeNode> {
private String name;
private boolean done;
private int priority;
private Date dueDate;
private String id;
public Task(@MapsTo("name") String name) {
this.name = name;
this.done = false;
priority = 0;
dueDate = new Date();
// Yes we should probably use a UUID here to ensure uniqueness,
// but this is good enough for our purposes...
this.id = Long.toString(System.currentTimeMillis());
}
public Task(Task that) {
set(that);
}
...
public boolean equals(Object obj) {
if (obj instanceof Task) {
Task that = (Task)obj;
if (!this.getName().equals(that.getName()))
return false;
if (this.isDone() != that.isDone())
return false;
if (this.getPriority() != that.getPriority())
return false;
if (!this.getDueDate().equals(that.getDueDate()))
return false;
return true;
}
return super.equals(obj);
}
public void set(Task that) {
this.name = that.name;
this.done = that.done;
this.priority = that.priority;
this.dueDate = that.dueDate;
this.id = that.id;
}
</pre>
<br />
Note the equals() override allows us to determine if a Task has changed (is dirty) in the Editor.
<br />
<br />
To simplify handling of the additional Rich Text field, I have defined a new Model object named TaskWithNotes. This is for client-side consumption only, so there's no need to provide any kind of <a href="https://docs.jboss.org/author/display/ERRAI/Marshalling">marshalling support</a> as we did with the other Model objects. And, here it is:</div>
<br />
<pre class="brush:java;">public class TaskWithNotes extends Task {
private String notes = "";
public TaskWithNotes(Task that) {
super(that);
}
public void setNotes(String notes) {
this.notes = notes;
}
public String getNotes() {
return notes;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TaskWithNotes) {
TaskWithNotes other = (TaskWithNotes)obj;
if (!this.getNotes().equals(other.getNotes()))
return false;
}
return super.equals(obj);
}
public Task asTask() {
return new Task(this);
}
}
</pre>
The constructor initializes the Task base class from a given Task object. An equals() override is used by the Presenter to determine if the object has changed (is dirty).<br />
<br />
Now, finally back to our TaskEditorView, which is responsible for setting the Task content into its various widgets. Here are the relevant bits of code:<br />
<br />
<pre class="brush:java;"> @Override
public void setContent(TaskWithNotes content) {
taskWithNotes = content;
nameTextBox.setText(taskWithNotes.getName());
doneCheckBox.setValue(taskWithNotes.isDone());
notesTextArea.setHTML(taskWithNotes.getNotes());
for (int index=0; index<priorityListBox.getItemCount(); ++index) {
int v = Integer.parseInt(priorityListBox.getValue(index));
if (v == taskWithNotes.getPriority()) {
priorityListBox.setSelectedIndex(index);
break;
}
}
dueDatePicker.setValue(taskWithNotes.getDueDate(), true);
dueDatePicker.setCurrentMonth(taskWithNotes.getDueDate());
}
@Override
public TaskWithNotes getContent() {
TaskWithNotes content = new TaskWithNotes(taskWithNotes);
content.setName(nameTextBox.getText());
content.setDone(doneCheckBox.getValue());
content.setNotes(notesTextArea.getHTML());
content.setPriority(Integer.parseInt(priorityListBox.getSelectedValue()));
content.setDueDate(dueDatePicker.getValue());
return content;
}
</pre>
The setContent() method saves the original TaskWithNotes object, for comparison with the (possibly) changed values, and then initializes the relevant widgets with the Task values.<br />
<br />
The getContent() method reads the widget values and builds a TaskWithNotes object. This can be used to determine if anything has changed, and to serialize the changes to our VFS.<br />
<br />
<h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcRf4LqHXrGV9PNKZPUairuzqBIUDLkattDKTm5wwyZLdkikbpT7slP0O_4vC1-n_Xtq29jGKPn7REkME4NBZXnN400XL59ZF5BVspmzXXGfzH2K2vHet5dj1-4Fi2nWZmPUiAhs4WOY0/s1600/ReturnToSender.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcRf4LqHXrGV9PNKZPUairuzqBIUDLkattDKTm5wwyZLdkikbpT7slP0O_4vC1-n_Xtq29jGKPn7REkME4NBZXnN400XL59ZF5BVspmzXXGfzH2K2vHet5dj1-4Fi2nWZmPUiAhs4WOY0/s1600/ReturnToSender.png" /></a>Returning Values</h2>
As before, the Editor still has the "OK" and "Cancel" buttons which are used to save/close the Editor. The question now is: how do we get the changed Task values back to the other Presenters/Views in our application? I've chosen to use a notification event, TaskChanged, which simply contains the updated Task object. The event is fired from the Task Editor if anything has changed. This is identical to the other event notification patterns we have already seen being used (TaskCreated, TaskDone, etc.)<br />
<br />
The ProjectsPresenter and TasksPresenter Screens both listen for this event: ProjectsPresenter updates the Task in TasksRoot and serializes the entire tree to the "tasks.json" file. TasksPresenter simply updates its View to show the changed values.<br />
<br />
<h2>
Good things don't come easy...or do they?</h2>
Wow, that was a little more involved than I originally thought, but totally worth it. I'd be interested to hear if there are better ways to accomplish <a href="https://github.com/bbrodt/uberfire-tutorial/tree/checkpoint-9">what I've hacked together here</a>. Comments welcome!Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com2tag:blogger.com,1999:blog-4008600950604647427.post-58396994552135525412014-11-04T02:24:00.002-08:002014-11-25T06:59:25.333-08:00JBoss BPMS Success Stories (and other fairytales)This year's <a href="https://www.eclipsecon.org/europe2014/">EclipseCon Europe </a>was held, as always, in Ludwigsburg Germany. Featured for the first time this year at the Ludwigsburg event was the UnConference - essentially a collection of presentations and discussions targeted at Eclipse Working Groups. Of course the SOA project was well represented at <a href="https://www.eclipsecon.org/europe2014/bpm">BPM Day</a> with several interesting talks by Marc Gille, Adrian Mos, Priska Buri, Gregor Gisler, Rodrigue Le Gall and, of course, yours truly.<br />
<br />
My presentation begins with a short overview of BPM Suite, and how it was used to meet real life challenges in different vertical markets. I conclude with a preview of what's new in jBPM version 6 and what's on the horizon for JBoss middleware technologies. The presentation is called “Customer Success Stories and other fairytales” because when I asked our Solution Architects to share some of their success stories, they agreed under the conditions that I <b>not </b>mention certain of our customers’ names, and I was not allowed to present any details of their internal business processes.
<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/41094038" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<b> <a href="https://www.slideshare.net/BobBrodt/bpms-ecu2014" target="_blank" title="Bpms ecu2014">Bpms ecu2014</a> </b> from <b><a href="https://www.slideshare.net/BobBrodt" target="_blank">Bob Brodt</a></b> </div>
<br />
<br />
Adrian's presentation, "From Domain Specific Process Design and Back" was particularly interesting to me. The premise is that the people that are most familiar with how their business is run, are typically not familiar with the technology required to automate their business processes, and will need to turn to an external group (or their internal IT department) for help. The technologist must first learn the language of the business through a series of meetings with the various stakeholders. These kinds of discussions usually involve whiteboard drawings, with boxes and circles and arrows. Eventually a kind of graphical language evolves, which captures the artifacts, resources and participants in the business workflow. The question then becomes: how can we accurately translate the semantics of this domain-specific graphical language, to the language of the BPM execution engine?<br />
<br />
Fortunately, all of the tools required to do this are already available in the open source community. In his presentation, Adrian points out that a graphical editor capable of creating drawings that capture the semantic in the domain language, can be created in a matter of minutes using <a href="http://eclipse.org/sirius/">Sirius</a>. The output from this editor is a text file that conforms to a Domain Specific Language (DSL) grammar which is easily defined by <a href="http://www.eclipse.org/Xtext/">XText</a>. This DSL can then be translated to BPMN2 (or any other execution language for that matter) using <a href="http://www.eclipse.org/mangrove/">Mangrove</a>. This completes the one-way translation from the graphical DSL to an executable language. The problem Adrian is still working on is the reverse translation from executable to DSL. While not a trivial problem, it should (theoretically) be possible to complete this round-trip, thus bridging the language gap.<br />
<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/41552160" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<b> <a href="https://www.slideshare.net/adrianmos/bpm-daytalkshared" target="_blank" title="From Domain-Specific Process Design to Execution and Back">From Domain-Specific Process Design to Execution and Back</a> </b> from <b><a href="https://www.slideshare.net/adrianmos" target="_blank">Adrian Mos</a></b> </div>
<br />
<a href="https://www.blogger.com/blogger.g?blogID=4008600950604647427" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"></a>Every year, Bonitasoft sponsors a one week long "think outside the box" event, where employees are asked to brainstorm ideas about which they are most passionate. Rodrigue Le Gall's presentation, "The BPM of Things" was a play on the "Internet of Things", or IoT, which also happened to be <b>the</b> hot-button topic of this year's EclipseCon event. Rodrigue predicted that in the year 2020, the number of connected, or "smart" objects, would exceed 80 Billion - nearly 9 times more than the number of smart phones in use on the planet. With all these smart devices on the planet, facilitating communications between them, and coordinating their individual or group behaviors could become problematic.<br />
<br />
Rodrigue envisions the use of a BPM engine to orchestrate the interactions of these devices. As a proof of concept, he devised a "shared locker" - essentially a publicly accessible depot for exchanging physical goods or information (think of a public locker you might find in a train station or bus terminal.) The actual hardware bits included a <a href="http://www.raspberrypi.org/">Raspbery PI</a> and some blinky lights to simulate the locking mechanism.<br />
<br />
If you're interested in implementation details and motivation behind this very interesting topic, please have a look at the slide deck of Rodrigue's presentation:<br />
<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/40917961" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<b> <a href="https://www.slideshare.net/rlg/bpm-of-things" target="_blank" title="Bpm of things or how to ">Bpm of things or how to </a> </b> from <b><a href="https://www.slideshare.net/rlg" target="_blank">rlg</a></b><br />
<br />
Community interest in the <a href="https://www.eclipse.org/bpmn2-modeler/">Eclipse BPMN2 Modeler</a> is growing steadily, and Gregor Gisler and the <a href="http://www.itpearls.com/">IT-Pearls</a> team have made a significant contribution in the form of an extension plug-in that customizes the editor for <a href="https://www.eclipse.org/stardust/">Eclipse Stardust</a>.This plugin has been on my list of things to do on a rainy weekend for quite a while now, but alas, I live in Colorado where we have an average of 300 days of sunshine per year ;)<br />
<br />
Thanks Gregor, Stephan and especially Simon (who, as I understand it, did most of the heavy lifting.)<br />
<br /></div>
<br />
<a href="https://www.blogger.com/blogger.g?blogID=4008600950604647427" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"></a><iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/40856471" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<b> <a href="https://www.slideshare.net/sufifus/eclipsecon-bpm-day-ludwigsburg" target="_blank" title="EclipseCon BPM Day Ludwigsburg - Roundtrip Modelling with Eclipse Stardust">EclipseCon BPM Day Ludwigsburg - Roundtrip Modelling with Eclipse Stardust</a> </b> from <b><a href="https://www.slideshare.net/sufifus" target="_blank">IT-Pearls AG</a></b> </div>
Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com2tag:blogger.com,1999:blog-4008600950604647427.post-37588322301733889882014-06-10T10:02:00.002-07:002014-06-24T09:42:00.182-07:00The Missing BPMN2 Modeler screencasts Hi folks!<br />
<br />
Several of you have been asking "what happened to the screencast videos on the <a href="https://www.eclipse.org/bpmn2-modeler/">Eclipse BPMN2 Modeler project website</a>?" To be honest, I don't know what happened. I know they are still there, but for some reason they won't play anymore. Maybe it has to do with a recent upgrade to Flash player, maybe it's aliens from outer space...who knows?<br />
<br />
Until I can get a chance to re-record these (and update them for the latest version) I've made them available here:<br />
<br />
<a href="https://sites.google.com/site/eclipsebpmn2modeler/BPMN2Modeler-Setup.swf?attredirects=1">Setup</a><br />
<a href="https://sites.google.com/site/eclipsebpmn2modeler/BPMN2Modeler-SimpleProcess.swf?attredirects=1">Simple Process</a><br />
<a href="https://sites.google.com/site/eclipsebpmn2modeler/BPMN2Modeler-ConnectionRouterFeature.swf?attredirects=1">Connection Router Feature</a><br />
<a href="https://sites.google.com/site/eclipsebpmn2modeler/CustomTask-Example-part1.swf?attredirects=1">Custom Task Example - Part 1</a><br />
<a href="https://sites.google.com/site/eclipsebpmn2modeler/CustomTask-Example-part2.swf?attredirects=1">Custom Task Example - Part 2</a><br />
<br />
Sorry if these are bit fuzzy, but if you resize your browser just right, they should be legible.<br />
<br />
Enjoy :)<br />
<br />
BobBob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com1tag:blogger.com,1999:blog-4008600950604647427.post-4758804786779721832013-09-06T13:38:00.000-07:002016-05-18T10:48:48.006-07:00Stealth Computers<h3>
Bookputer</h3>
Remember back in grade school when you were the first person in history, ever, to come up with the idea of hollowing out a book to hide your Playboy magazines so Mom wouldn't find them? The concept still applies today, but now you have to hide your PC in a hollow book so Mom won't find all those porn websites you've been surfing.<br />
<fill blank="" in="" the=""><br /></fill>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrVJOzUJ0iuO3hFec4L0On0yF0rlkfdr6ZmSstUV-hxi3tMjJPoXqCr5odegbGo4HlLzRTtru9Q3sBhJMYz_ME0mePhZWnQWj824xoqU851pcaun4t3pbmBoz-o84Q2dt4OPDYLSTCFno/s1600/Book-PC.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrVJOzUJ0iuO3hFec4L0On0yF0rlkfdr6ZmSstUV-hxi3tMjJPoXqCr5odegbGo4HlLzRTtru9Q3sBhJMYz_ME0mePhZWnQWj824xoqU851pcaun4t3pbmBoz-o84Q2dt4OPDYLSTCFno/s640/Book-PC.jpg" style="cursor: move;" /></a></div>
<h3 style="clear: both; text-align: left;">
Beerputer</h3>
<div class="separator" style="clear: both; text-align: left;">
I like beer. I like computers. Hey! Why not combine them???</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUQ6m_ophU-uz2DqTPU98PFIvxYIebMtxJJMujoGzsZPeZt72EfBR7nilOwbVSrl3Msdv6pQIwrgXndijA8bhA6I4caPoM3hlXEj5rPOP9UMORZJVRZiwA1eVXgTMALDE4Zo1CQIZA0YY/s1600/Newcastle-side.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUQ6m_ophU-uz2DqTPU98PFIvxYIebMtxJJMujoGzsZPeZt72EfBR7nilOwbVSrl3Msdv6pQIwrgXndijA8bhA6I4caPoM3hlXEj5rPOP9UMORZJVRZiwA1eVXgTMALDE4Zo1CQIZA0YY/s1600/Newcastle-side.JPG" width="180" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHNCGQKhhmpOCOCiRKkhQyDhlc4iOEtkFT-SYZoR2gGyhCaO_tqrLJ-jJrFTy6NwSSrgHUrJcsUMpCzVdGGLt0FUh7s4FHNKh1n_EBN6k9hbf2ThnYbx34M-iHBukDw_yLQVEe0kQN5IY/s1600/Newcastle-front.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHNCGQKhhmpOCOCiRKkhQyDhlc4iOEtkFT-SYZoR2gGyhCaO_tqrLJ-jJrFTy6NwSSrgHUrJcsUMpCzVdGGLt0FUh7s4FHNKh1n_EBN6k9hbf2ThnYbx34M-iHBukDw_yLQVEe0kQN5IY/s1600/Newcastle-front.JPG" width="180" /></a></div>
<br />
<h3>
Steampunkputer</h3>
Here's my steampunk inspired creation; an intel Core i7 with a really fast SSD and two GEFORCE display cards running 4 monitors.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyJCR3GNGTq1fHwRoDRc19pmXwYcGw5PMKtj5i-cNO3P0BwI_q2b3v59ELYKT1t_HCxNVQUaOTwlZGXeKv2w38JiU5ydlGiW9vYw-pWTKQEJkxltAnHpMjrPG3KfoMA0MIdtCMIAW7VIQ/s1600/DSC00171.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyJCR3GNGTq1fHwRoDRc19pmXwYcGw5PMKtj5i-cNO3P0BwI_q2b3v59ELYKT1t_HCxNVQUaOTwlZGXeKv2w38JiU5ydlGiW9vYw-pWTKQEJkxltAnHpMjrPG3KfoMA0MIdtCMIAW7VIQ/s320/DSC00171.JPG" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<h3>
Pigputer</h3>
Also known as "Hamilton" (get it? <b>Ham</b>ilton? HAM-ilton?) this fiberglass piggy-bank is a great conversation starter. Just don't try to stuff coins in there - they <b>will</b> cause Hamilton to spew sparks and release the magic black smoke.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaIbH23kNyOreBm7nAbu8r193y00EiGgOi3EDtqQz0I3R0M2GKtx-TxRxBbmNSSSjdOr-CTKLT8IuqWmy2NkwWq6ZnTJVc3X22VoIogTXsbS3sgmxvayPqZuv2BX8EsWgmuxN-j1dapME/s1600/006.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: right;"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaIbH23kNyOreBm7nAbu8r193y00EiGgOi3EDtqQz0I3R0M2GKtx-TxRxBbmNSSSjdOr-CTKLT8IuqWmy2NkwWq6ZnTJVc3X22VoIogTXsbS3sgmxvayPqZuv2BX8EsWgmuxN-j1dapME/s320/006.JPG" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvnSWu9yxUpcqWege0teqQDAR4lPZALNy6c1j0ZZyf0IDV1vcbCwcTQ-GP7YAjPu4IZzoAWb2_IIPiHQTvj-VR1qP2iLAxAbNH3YuKf0K1LUdit1ils8Nf5ryQcR3O0vsyHrVM0ddtFaI/s1600/002.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"><img border="0" height="209" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvnSWu9yxUpcqWege0teqQDAR4lPZALNy6c1j0ZZyf0IDV1vcbCwcTQ-GP7YAjPu4IZzoAWb2_IIPiHQTvj-VR1qP2iLAxAbNH3YuKf0K1LUdit1ils8Nf5ryQcR3O0vsyHrVM0ddtFaI/s320/002.JPG" width="320" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaIbH23kNyOreBm7nAbu8r193y00EiGgOi3EDtqQz0I3R0M2GKtx-TxRxBbmNSSSjdOr-CTKLT8IuqWmy2NkwWq6ZnTJVc3X22VoIogTXsbS3sgmxvayPqZuv2BX8EsWgmuxN-j1dapME/s1600/006.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: right;"><br /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaIbH23kNyOreBm7nAbu8r193y00EiGgOi3EDtqQz0I3R0M2GKtx-TxRxBbmNSSSjdOr-CTKLT8IuqWmy2NkwWq6ZnTJVc3X22VoIogTXsbS3sgmxvayPqZuv2BX8EsWgmuxN-j1dapME/s1600/006.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: right;"><br /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaIbH23kNyOreBm7nAbu8r193y00EiGgOi3EDtqQz0I3R0M2GKtx-TxRxBbmNSSSjdOr-CTKLT8IuqWmy2NkwWq6ZnTJVc3X22VoIogTXsbS3sgmxvayPqZuv2BX8EsWgmuxN-j1dapME/s1600/006.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: right;"><br /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0tag:blogger.com,1999:blog-4008600950604647427.post-30376242286634596842013-09-04T09:56:00.003-07:002013-09-04T09:56:41.853-07:00BPMN 2.0 Diagram Types<h3>
Eclipse BPMN2 Modeler and Diagram Types</h3>
As a software developer involved in implementing a BPM system for your organization or for a customer you're probably focused mainly on defining user roles, work activities, process flow logic, exception handling, making sure all of the data structures are properly defined and all of the endpoints are correct, and yadda yadda yadda. Sometimes it feels like you're too close to the trees to see the forest.<br />
<br />
The smart folks that make up the OMG understand that business processes can be extremely complex and that no one type of diagram can capture all of the details required to fully understand the inner workings of a large enterprise, much less how one organization interacts with others in the business world. That's why the BPMN 2.0 specification defines several types of diagrams, which present different views of a business process. Today I'll discuss these diagram types and how they're intended to be used.<br />
<h3>
Process Diagrams</h3>
This is the "boxes and arrows" flow chart type of diagram that defines the activities ("boxes") their sequencing ("arrows"), decision branches ("diamonds") and so on. This type of diagram typically represents an Organization's private process, i.e. a description of how an Organization works internally. While the diagram may show information (in the form of "messages") coming in from, or leaving the Organization to the outside world, this type of diagram is not intended to show interactions between different Organizations.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
It's also possible to describe two or more departments interacting with each other inside the Organization using "swim lanes". Swim lanes can be nested to reflect the Organization's departmental hierarchy and individual roles within a department. Here's an example showing the product development cycle in a software consulting firm:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEa_g0C3wFoUsTLaUN6n3bCOweyTrBm5-6yKCM7qC97NoS-4t8mleUox58lIxHZsnKbbcVUT53YlSSjLYU-oTE9lT_wc1uFcixavzkxJ7uZsvlfqBuPVa5wLTlni3UrP1KQtmym9Ly5j4/s1600/ProcessDiagramWithLanes.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"></a><a href="http://www.blogger.com/blogger.g?blogID=4008600950604647427" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"></a><img border="0" height="368" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEa_g0C3wFoUsTLaUN6n3bCOweyTrBm5-6yKCM7qC97NoS-4t8mleUox58lIxHZsnKbbcVUT53YlSSjLYU-oTE9lT_wc1uFcixavzkxJ7uZsvlfqBuPVa5wLTlni3UrP1KQtmym9Ly5j4/s640/ProcessDiagramWithLanes.jpg" width="640" /><a href="http://www.blogger.com/blogger.g?blogID=4008600950604647427" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"></a></div>
<br />
<h3>
Collaboration Diagrams</h3>
This type of diagram shows the interactions between two or more processes, typically owned by different parties or Organizations. The processes are represented by "Pools" and, as with Process Diagrams, each Pool may contain one or more Lanes. Collaboration Diagrams are similar to Process Diagrams in that they depict the flow of activities internal to an Organization; the difference is whereas a Process Diagram is used to depict a single process, Collaboration Diagrams show multiple processes as well as the interface points between them.<br />
<br />
Here's a Collaboration Diagram illustrating a Pizza order:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdYG1sGpFYrFyHJaMCABqaterrKehgRptnqHDwyyL1qLS4N5Vzm-UEXfTvTuiEsyYAY4mJvbyocqishixRX8f1qQxzoxzZMbUdyofCjOa5zLnmJmIcihDsXUJ2Sd0mtpE59uweDjQOLck/s1600/CollaborationDiagramType.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="444" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdYG1sGpFYrFyHJaMCABqaterrKehgRptnqHDwyyL1qLS4N5Vzm-UEXfTvTuiEsyYAY4mJvbyocqishixRX8f1qQxzoxzZMbUdyofCjOa5zLnmJmIcihDsXUJ2Sd0mtpE59uweDjQOLck/s640/CollaborationDiagramType.png" width="640" /></a></div>
<br />
<h3>
Choreography Diagrams</h3>
Choreography Diagrams are mainly focused on the participants ("Pools") in a business process and the information exchanged between them, rather than on the orchestration of the work being performed. A Choreography Diagram can be thought of as a business contract between two or more Organizations.<br />
<br />
Here's a Choreography Diagram showing the interaction between a buyer and an online retailer. Each exchange is represented by a rounded rectangle, called a Choreography Task; the information exchanged between them is shown as an envelope representing a message sent from the initiating participant.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSu5yUs-gdTg1XjAM1ZuyLgKYbz9m0pISYNHdUeuiA8RwqpP0njikbtmir_vP5NM57xWmXhD3cc21H9IjeiZ6gRn6lT8eCp8x6rmfDjhuIgTVfvaD2ISklxeVVXUS7KvM6IoCnhzxWEqU/s1600/ChoreographyDiagramType.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSu5yUs-gdTg1XjAM1ZuyLgKYbz9m0pISYNHdUeuiA8RwqpP0njikbtmir_vP5NM57xWmXhD3cc21H9IjeiZ6gRn6lT8eCp8x6rmfDjhuIgTVfvaD2ISklxeVVXUS7KvM6IoCnhzxWEqU/s640/ChoreographyDiagramType.png" width="640" /></a></div>
<br />
<h3>
Conversation Diagrams</h3>
<a href="http://www.blogger.com/blogger.g?blogID=4008600950604647427" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"></a>These are a simplification of a Choreography Diagram and are intended as an overview to illustrate which participants co-operate on which tasks. Conversations, as you would guess, are exchanges of packets of information ("Messages") related to the completion of a task.<br />
<br />
In Conversation Diagrams, the participants are represented as Pools, similar to the Collaboration Diagram, and the information exchange (Conversation) as a hexagon connecting them, as shown here:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKmxb2dEOlRbWWHulQj8_qRsNvQCvKX4F2g3zIhtaPAyxi4G-1slFheIavMYT5UYkX6ysnI8H8l2NFOOw6u1SBwr9lBZikOQmIX1ZtixufyoQ9pk7-g86B1nWwLvuoS-p7xd_pCum_7wE/s1600/ConversationDiagramType.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKmxb2dEOlRbWWHulQj8_qRsNvQCvKX4F2g3zIhtaPAyxi4G-1slFheIavMYT5UYkX6ysnI8H8l2NFOOw6u1SBwr9lBZikOQmIX1ZtixufyoQ9pk7-g86B1nWwLvuoS-p7xd_pCum_7wE/s320/ConversationDiagramType.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
Conversation Diagrams are not yet fully supported by the Eclipse BPMN2 Modeler, but they are planned as an enhancement for a future release. Please visit the <a href="http://eclipse.org/bpmn2-modeler/">project website for more information about the BPMN2 Modeler</a>.Bob Brodthttp://www.blogger.com/profile/13136885464012937245noreply@blogger.com0