cmd;
try {
if (editMode == EditMode.CREATE) {
* cmd = new CreateDatasetCommand(dataset, session.getUser());
} else {
cmd = new UpdateDatasetCommand(dataset, session.getUser());
}
dataset = commandEngine.submit(cmd);
...
} catch ( CommandException ex) {
...
}
return "/dataset.xhtml?id=" + dataset.getId() + ...
+ "&faces-redirect=true";
```
.bottom-remark[
Code adapted for slide.
]
???
Here's the front end, JSF. We're still submitting the same CreateDatasetCommand to the engine if we're in "create" mode.
If we're in "edit" mode we send the UpdateDatasetCommand. In both cases we get back a dataset but we're first deciding which command to send.
This is the "code as data" aspect of the command pattern at work. The command is simply stored as a variable, but you can imagine the command being put in a queue. Again, a command is a first class object you can pass around.
---
# Command Engine in a Wild Loop
Listing the content of a dataverse object (think `ls`).
```java
try {
for ( DvObject o :
* engineSvc.submit(new ListDataverseContentCommand(u, dataverse)) ) {
// add o to the output
}
} catch (IllegalCommandException ex) {
return errorResponse( Response.Status.FORBIDDEN, ... );
} catch (PermissionException ex) {
return errorResponse(Response.Status.UNAUTHORIZED, ... );
} catch (CommandException ex) {
logger.log(Level.SEVERE, "Error while " + messageSeed, ex);
return errorResponse(Status.INTERNAL_SERVER_ERROR, ... );
}
```
.bottom-remark[
Code adapted for slide. Actual code has some extra neat stuff outside the scope of this BOF.
See https://github.com/IQSS/dataverse/blob/master/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java#L123
]
???
We mentioned that our version of "execute" is an expression rather than a statement. What does this mean in practice?
Since expressions can go almost anywhere (as opposed to statements), we can do this nice enhanced for-loop, where we create, submit and execute the command in the loop's head.
There's a lot of flexibility in how we make use of the engine.
---
# Command Sequence Diagram
.center[
]
???
We were wondering how to cram commands into our sequence diagram. Here's how.
We've still got JSF talking to a backing bean. But now the backing bean submits commands to the engine and the commands manipulate the model.
---
# Command Sequence Diagram
.center[
]
Hey, where did the service beans go?
/
???
Of course, now you might be asking yourself, "Where did the service beans go?"
---
# Command Sequence Diagram
.center[
]
Hey, where did the service beans go?
/
Service beans will be with you, always.
???
Obiwan says, "Service beans will be with you... always."
Will they? What does *that* mean?
Was Uncle Owen right? Is that wizard just a crazy old man?
---
# Sample Command: Rename a Dataverse
.smaller[```java
@RequiredPermissions( Permission.UndoableEdit )
public class RenameDataverseCommand
extends AbstractCommand{
private final String newName;
private final Dataverse renamed;
public RenameDataverseCommand( User aUser,
Dataverse aDataverse, String aNewName ) {
super( aUser, aDataverse );
newName = aNewName;
renamed = aDataverse;
}
@Override
public Dataverse execute(CommandContext ctxt) throws CommandException {
if ( newName.trim().isEmpty() ) {
throw new
IllegalCommandException("Dataverse name cannot be empty", this);
}
renamed.setName(newName);
return ctxt.dataverses().save(renamed);
}
}
```]
.bottom-remark[ Actual code]
???
So where are the service beans?
Let's look at the RenameDataverseCommand.
Once we get past a validation check (which is an example of an IllegalCommandException) we see "context dot dataverses dot save".
That "save" is a method on the DataverseServiceBean and we get to it through what we're calling the CommandContext.
So the service beans are still with us. But they're in charge of relatively simple operations like "save".
The next thing to notice about the way we've implemented commands is that we annotate them with required permissions.
This command requires the UndoableEdit permission.
A PermissionException is thrown if user lacks sufficient permission
---
# Permissions and Commands
* In code, permissions live in an enum. Each permission:
* Holds a basic descriptive text.
* States which objects it applies to.
* In the database, permission live in a bit field
* Very fast, but must be kept under 64.
* No DB joins needed
* Testing if a permission exists in a permission set is a bitwise operation.
.smaller[```java
public enum Permission {
Discover("See and search content", DvObject.class),
Download("Download the file", DataFile.class),
AccessUnpublishedContent("Access unpublished content",
DvObject.class),
AccessRestrictedMetadata("Access metadata marked as\"restricted\"",
DvObject.class),
UndoableEdit("Edits that do not cause data loss", DvObject.class),
DestructiveEdit("Edits that cannot be reversed, such as deleting data",
DvObject.class),
// ...
```]
???
Let's look a little deeper at permissions.
We put all of our permissions in an enum. That enum contains a description of what the permission means and it specifies which objects the permission can be applied to.
How do we store permissions in the database?
For performance, we store permissions in a bit field backed by a long. This is kind of like how Unix permissions work, how you chmod a file to 755 or 644. Because permissions are in an enum, each permission has a unique index number we use to map it to a bit in the field.
It means we don't need expensive database joins when we're checking permissions. Testing if a permission exists is a bitwise operation.
---
# Supporting Multiple Receivers
When commands involve more than a single receiver, the `RequiredPermissionMap` annotation can be used.
```java
@RequiredPermissionsMap({
@RequiredPermissions( dataverseName = "moved",
value = {Permission.UndoableEdit, Permission.GrantPermissions} ),
@RequiredPermissions( dataverseName = "source",
value = Permission.UndoableEdit ),
@RequiredPermissions( dataverseName = "destination",
value = Permission.DestructiveEdit )
})
public class MoveDataverseCommand extends AbstractVoidCommand {
// ...
public MoveDataverseCommand( User aUser,
Dataverse moved, Dataverse destination ) {
super(aUser, dv("moved", moved),
dv("source",moved.getOwner()),
dv("destination",destination) );
this.moved = moved;
this.destination = destination;
}
```
???
We've shown how we annotate our commands with the required permission but what if the command operates on more than one object at a time? What if we want to specify that different permissions are required on different objects that are being manipulated by the command?
No problem. We use a RequiredPermissionMap to specify that you need the "DestructiveEdit" permission on this object and different permission on a different object.
---
# Command Composition
A dataset has a published version, accessible by everyone, and a draft version, accessible by the team only. We composed existing commands to get the latest version accessible to the `User` issuing the `Command`:
.smaller[```java
@RequiredPermissions( Permission.Discover )
public class GetLatestAccessibleDatasetVersionCommand
extends AbstractCommand
// ...
@Override
public DatasetVersion execute(CommandContext ctxt) throws CommandException {
DatasetVersion d = null;
try {
d = ctxt.engine()
* .submit(new GetDraftDatasetVersionCommand(u, ds));
} catch(PermissionException ex) {}
if ( d == null ) {
d = ctxt.engine()
* .submit(new GetLatestPublishedDatasetVersionCommand(u,ds));
}
return d;
}
```]
???
What about composing commands? Calling commands from other commands. This is supported too, and here's an example.
The parent command first submits the GetDRAFTdatasetCommand to the engine.
Note the empty catch clause for a `PermissionException`. If the issuing does not have a permission to view the draft, we try to get the published one. The null check is there in case there isn't a draft version. But, you know academia, there *always* is a draft version.
We get some nice code re-use here, calling commands from commands.
---
# Easy Testing
Since the command and the context are POJOs, we can mock them easily.
.smaller[```java
//...
@Before
public void setUp() {
testEngine = new TestDataverseEngine( new TestCommandContext(){...});
//...
@Test
public void testValidMove() throws Exception {
testEngine.submit(
new MoveDataverseCommand(null, childB, childA));
assertEquals( childA, childB.getOwner() );
assertEquals( Arrays.asList(root, childA), childB.getOwners() );
}
@Test( expected=IllegalCommandException.class )
public void testInvalidMove() throws Exception {
testEngine.submit(
new MoveDataverseCommand(null, childA, grandchildAA));
fail();
}
```]
???
Here I'd like to mention the testability of the code. We can mock the command context and check to make sure IllegalCommand exceptions are thrown. For example, if you issue a command trying to move a folder to its child... that's simply not allowed.
You can't have /usr/local/bin and move "usr" under "local".
The TestCommandContext class returns `null` for all the beans except for those relevant to the test. For these beans, it returns a minimal implementation where only relevant methods work.
---
class: slide-emph-mid middle
Given that:
* Service beans act on model objects,
* Commands act on model objects, and
* Commands work well for us,
# Can we remove all service beans?
???
So, service beans act on model objects.
Commands act on model objects.
Was Obiwan right? Will service beans be with us, always?
Can we remove the service beans?
---
## Can we remove all service beans?
# Yes
* Command context can give direct access to the entity manager, JMS resources and the like, so commands could use them directly.
* There's just one problem...
???
The answer is yes, unless you consider the engine to be a service bean.
We consider the engine part of our command pattern adaptation.
At any event, we need to allow the container to inject dependencies somewhere, and that somewhere must be a managed bean. We later reference these injected dependencies from the `CommandContext`.
From the CommandContext we can directly access the EntityManager and JMS resources...
But there's one problem...
---
## Can we remove all service beans?
# Yes
* Command context can give direct access to the entity manager, JMS resources and the like, so commands could use them directly.
* There's just one problem...
.center.problem[
It's the Wrong Question
]
???
It's the wrong question.
---
class: slide-emph-mid middle
Given that:
* Service beans act on model objects,
* Commands act on model objects, and
* Commands work well for us,
# .deleted[Can] Should we remove all service beans?
???
Not *can* but *should* we remove all the service beans? That's really the question.
---
# Should We Remove All Service Beans?
## Probably Not
We tried that. Didn't work well, since the commands became too detailed.
## Current Status - a more balanced approach
**Commands** deal with: Operations on model objects.
**Service Beans** deal with: save, update, delete and various lookups of model objects (e.g. findById).
???
Probably not. We tried it and commands became too detailed.
We're still iterating on this.
Right now we're thinking...
- Commands should deal with operations on model objects
- Service beans should deal with basic CRUD operations: create, read, update, delete.
---
# Should We Remove All Service Beans?
## Probably Not
We tried that. Didn't work well, since the commands became too detailed.
## Current Status - a more balanced approach
**Commands** deal with: Operations on model objects.
**Service Beans** deal with: save, update, delete and various lookups of model objects (e.g. findById).
.right[Hence, we call this:]
???
We're still trying to discover the pattern. For now we're calling it...
---
class: slide-emph-happy center middle leanbeanfont
## _the_
# Lean Bean Design Pattern
???
... the Lean Bean Design Pattern!
---
# Lean Beans (are made of this)
* Actions on models done by Command objects
* CRUD done by lean beans
## Benefits
* Code as data
* Reuse commands from various places
* Permission validation baked into the system
* Commands are POJOs:
* Reusable outside of Java EE
* Testable using JUnit
* Since we use beans, which are easier to mock than EntityManagers
* Easy to find functionality - look at the class' name
???
Again,
* Actions on models are done by Command objects
* CRUD is done by lean beans
The benefits include:
* Treating code as data
* Reusing commands from various places
* Having permission validation is baked into the system
Commands are POJOs (Plain Old Java Objects) which means they are:
* Reusable outside of Java EE
* Testable using JUnit
Finally, it's easy to find functionality. Just look at the class names of commands.
---
# Lean Beans (are made of this)
* Actions on models done by Command objects
* CRUD done by lean beans
## Downside
* Some infrastructure needed
* Engine
* Permission annotations
* Requires some learning - not a mainstream solution
.btw[Also, we're just starting this - so not a lot of experience yet.]
???
These are the downsides we've notice so far.
First, you need to write some infrastructure code. We wrote the command engine. We put in the checks for annotations on permissions.
In addition, from what we can tell, using the command pattern in Java EE is not especially mainstream. You may need to explain it to your developers.
---
# Future Work
Some issues we already found out, and will deal with soon:
### As Annotations are static, required permissions can't be dynamic
This conflicts with, e.g. The Decorator pattern. We will use a static-dynamic combo, where the basic command implementation uses reflection to return the required permissions, but subclasses can override this behavior.
### Permission pre-flight check
Current implementation requires an actual object to work on, but the database layer allows for permission checks using the entity's id only - no real need to retrieve the object. When it makes sense, we need to take advantage of this. Somehow.
???
Let's talk about future work. Where are we going with the Lean Bean pattern?
First, we're not sure we like using static annotations for required permissions. We're considering a static-dynamic combination where the basic command implementation uses reflection to return the required permission. But then we'd allow subclasses to override this behavior.
Second, we'd like to get away from always requiring an instantiated object to check permissions. Consider the case of `GetDatasetCommand`. This command requires a `Dataset` object, which is retrieved from the database with all the overhead this may mean. After the retrieval, the engine might conclude that the user does not have a permission to view this dataset, and disposes of that object. So all the hard work done by JPA to construct this object is lost, and the garbage collector has more work to do. For these cases, we'd like to be able to use just the object id, not the object itself.
---
class:sw3d-slide
.sw3d[
# Questions?
.sw3d-titles[
.sw3d-titlecontent[
Visit the IQSS data science team at http://datascience.iq.harvard.edu
Dataverse project @ GitHub: https://github.com/IQSS/dataverse
Slides and sample code from this talk: http://iqss.github.io/javaone2014-bof5619
.center[[![IQSS logo](images/iqss-logo.png)](http://www.iq.harvard.edu)]
Presentation created using remark.js.
UML drawings done with PlantUML.
Star wars scroller using CSS3: Based on Craig Buckler's implementation (for sitepoint.com) http://www.sitepoint.com/css3-starwars-scrolling-text/
Enjoy other IQSS BOFs at this JavaOne:
BOF5475 When The PrimeFaces Bootstrap Theme Isn’t Enough
Tuesday, Sep 30, 9:00 PM - 9:45 PM - Hilton - Plaza A
CON5575 Bean Validation: Practical Examples from a Real World Java EE7 Application
Tuesday, Sep 30, 4:00 PM - 5:00 PM - Parc 55 - Cyril Magnin I
.center[![Chewie and Han](images/chewiehan.jpg)]
]
]
]
---
class: slide-emph-happy middle center
#Thanks
Visit the IQSS data science team at http://datascience.iq.harvard.edu
Dataverse project @ GitHub: https://github.com/IQSS/dataverse
Slides and sample code from this talk: http://iqss.github.io/javaone2014-bof5619
Next up from IQSS:
Mike Heppler on JSF, PrimeFaces and Boostrap - Right here at Plaza A
[![IQSS logo](images/iqss-logo.png)](http://www.iq.harvard.edu)
.bottom-remark[
Presentation created using remark.js. UML drawings done with PlantUML.
Star wars scroller using CSS3: Based on Craig Buckler's implementation (for sitepoint.com)
http://www.sitepoint.com/css3-starwars-scrolling-text/
]