Table of Contents
Part One – The Application
Part Two – XPages Advanced 1
Part Three: XPages Advanced Database Separation
Part Four: XPages Advanced Document Wrapper
Part Five: XPages Advanced MVC Controller
Part Six: Use of OpenNTF Domino API
Part Seven: OSGi Application Development Introduction
Part Eight: OSGi JAX-RS REST Access with ODA
Part Nine: OsgiWorlds
Part Ten: Vaadin
Part Eleven: OsgiWorlds Document Wrapper
Part Twelve: Key Date Page
Part Thirteen: Application Sctructure
Now that I’ve covered database separation, it’s time to cover the biggest part of the move from XPages to web application – wrapping the Document in a serializable Java object that better manages editability of components and writing back to the underlying document.The source code can be viewed on GitHub or by downloading the database from OpenNTF.
First of all, let’s back up to the previous manner, which most XPages developers will be used to, the dominoDocument datasource. That is not just a datasource attached to an XPage, Custom Control or Panel. As with anything else in the XPages world, it’s actually a Java class, com.ibm.xsp.model.domino.wrapped.DominoDocument, which does exactly what it says, it wraps a Domino document. As with scoped variables, Domino objects cannot be stored so they’re accessible across partial refreshes, so this object basically loads all field values and anything else required to retrieve the backend document when a save is requested. So the concept I’m covering here is actually one all XPages developers are already familiar with.
Before going further, there are two key aspects handled by the dominoDocument datasource that this implementation doesn’t cover. The first is managing attachments. Within XPages and beyond, uploading attachments is two separate processes: storing temporarily on the server and attaching to the backend document. The File Upload control handles the former and, beyond XPages, that will be standard for the chosen technology. The latter has been done by Domino developers for decades. Once you think of it in those two terms, putting them together is pretty straightforward. If you want to store it in a bean, here’s a good answer from XPages Java guru Mark Leusink. If you want to handle all elements, most have been answered, as you can find with a quick Google search.
The other aspect not covered is “Rich Text”. My recommendation would be to break away from the mind-set of Rich Text as known from Notes Client development. Round-tripping does not have fidelity and, if you need that, then you need a third party product. In just XPages, the Rich Text control just stores HTML with a MIME type. Outside XPages and Domino, similar editors as well as the CKEditor are available, but I would expect it’s just stored as MIME and displayed as HTML. So thinking of it as a Rich Text Item is likely to confuse, because XPages doesn’t use RichTextStyles or RichTextParagraphStyles etc, it just stores HTML paragraphs, font tags etc. And when you move beyond, you would not use a RichTextItem in the way Notes Client development has.
In this example, I will be using a managed bean, as can be seen from the faces-config in the database, with the variable name keyDateBean stored in viewScope and mapped to the Java class uk.co.intec.beans.KeyDateBean. There are plenty of blog posts or videos already about managed beans, but the key thing to remember is that it is just an instance of a Java class, stored in a particular scope, and allocated a variable name. So the first time that variable name is encountered, it looks for an instance of the Java class in the relevant scope; if found, it uses it; if not, it creates it.
The KeyDateBean class itself is very small, with three methods –
postSave(). The crucial bit of information is that it extends com.timtripcony.AbstractSmartDocumentModel().
The strength of Java over custom controls or SSJS functions is that you can extend other Java classes. So you can build a Java class that adds core functionality and then either override it or add additional functionality. The com.timtripcony classes show that in abundance. These classes are all based on a document wrapper created by Tim Tripcony in a NotesIn9 series (see part one, part two, part three and part four). The source code for that is available on Tim’s BitBucket HowYaBean project. You can compare the source code for Key Dates here.
I’ve made a number of enhancements. The first is in AbstractMapModel (which allows the developer to access fields from the document using Expression Language without needing to create every field as a property). The
getValues() method, which is used to load the fields, uses
CaseInsensitiveMap. Anyone who has a pre-XPages Domino database or has LotusScript agents in the database knows that any additional or new fields may not be created with the same case sensitivity of the Form. That could mean one document has a field called “Name”, another “name”, another “NAME”. When accessing via code, that’s normally not a problem, the core API will adapt accordingly. When the field names are stored in a normal
HashMap, when you retrieve one called “Name”, it will not find “name” or “NAME”.
CaseInsensitiveMap gets around that problem.
In AbstractDocumentMapModel, which extends
AbstractMapModel, I’ve made a lot more changes. The biggest is to use OpenNTF Domino API. To see the biggest impact of that and an example of how things are much easier with the API, compare the
replaceItemValue() method in the two classes. In Tim’s it’s over 100 lines long. In the OpenNTF Domino API version I use, it’s not even there: it’s in the core API and enables things like storing whole Java objects in a field. Although it’s a single method, it hooks into a huge amount of other code spread throughout the API.
I’ve also added a number of additional properties to tie in with XPages functionality –
readOnly. As a result, the constructor is overloaded (additional versions added – Java doesn’t use optional parameters like SSJS or LotusScript) to allow those parameters to be passed. There is also a
wrappedDoc property to get the
dominoDocument object created and a
fieldTypeMap property. The latter is a
HashMap where the key is a field name and the value is READERS, AUTHORS or NAMES. One of the big limitations of the
dominoDocument datasource is that if you want to store a field as a Readers field, you have to manipulate it after the save. But because our wrapper is just interacting with the backend document in the database, initially saved by calling
Database.createDocument() and creating Items in that document, we can set the item type as Readers etc at the same time. That’s what our
save() method does, also setting it as a response document if
parentUnid is not blank. There is also an overloaded
save(boolean) method that allows you to return “xsp-current” or “xsp-success”, which allows the developer to either stay on the current page or maps to a Navigation Rule on the XPage to determine the next XPage to go to if the save is successful.
ignoreList property has been extended to ignore any field starting “$” and the “MIME_Version” field. And when the
load method calls
setValue(), it also checks for the field value as “” and converts it to “”. That’s because
dominoDocument.getValue() called on a blank field will return an empty Vector which, converted to a String on save, becomes “”. We don’t want that, we want “” instead. So this handles that. The final addition is a
getFormDescription() method, which allows a textual description of what the class is for, useful for displaying as the header of a Form Table.
The final class, which extends
AbstractDocumentMapModel, is AbstractSmartDocumentModel. This has some additional properties –
(because muscle memory makes me write that instead of
beDoc to get the underlying Document object. The constructor is overloaded again, to get the
editDocument URL parameter. Finally, there are a host of helper methods for retrieving field values in various formats -
As mentioned before, the KeyDateBean class just extends
AbstractSmartDocumentModel. So it just defines return values for
getFormDescription. Finally, in the
postSave it writes the customer name selected for this document to the relevant applicationScope variable. That means we have a comprehensive applicationScope variable for the customers in the application whenever a document is saved. Admittedly it doesn’t handle removing customer names, which is why it’s not used on the KeyDate XPage itself, but it demonstrates the kind of functionality that could be done in a postSave. (And OpenNTF Domino API offers an alternate approach for executing code after a save to a Document, namely Database listeners.)
And, although it’s not done here, this same wrapper could be used for updates triggered from outside the XPage where this managed bean is used, just by creating an instance of the class based on a specific document UNID. This is the real benefit, using a single interface from back-end or front-end, albeit with the trade-off that you would be loading all the field values into a separate Java object if interacting with this wrapper back-end rather than the underlying Document object.
Next time I’ll cover the “page controller” class that manages the UI and keep the XPage’s SSJS to a minimum.