Factories in XPages
Wed Nov 12 18:27:14 EST 2014
In my last post, I intimated that I wanted to write a post covering the concept of factories in XPages, or at least the specific kind in question. This is that post.
The term "factory" covers a number of meanings, and objects named this way crop up all over the place (ODA has one, for example). The kind I care about today are those defined in an XspContributor
object in an XPages plugin. These factories are generally (exclusively, perhaps) used for the purpose of generating adapters: assistant objects that allow the framework to perform operations on object types that may have no knowledge of the framework at all.
The way this generally takes form is this: when the framework needs to perform a specialized task, it asks the application (that is to say, the Application
object that controls the XPages app) for a list of factories it knows about that conform to a given interface:
FacesContext facesContext = FacesContext.getCurrentInstance(); ApplicationEx app = (ApplicationEx)facesContext.getApplication(); List<ComponentMapAdapterFactory> factories = app.findServices(COMPONENT_MAP_SERVICE_NAME);
Once it has this list, it loops through all of them and passes the object it's trying to adapt. If the factory is written to understand that type of object, it will return an adapter object; if not, it will return null:
ComponentMapAdapter adapter = null; for(ComponentMapAdapterFactory fac : factories) { adapter = fac.createAdapter(object_); if(adapter != null) { break; } }
The framework can then use that adapter to perform the action on the object, without either the framework or the object knowing anything about the other:
for(String propertyName : adapter.getPropertyNames()) { // ... }
XSP internally uses this for a great many things. One use that I've run into (and butted heads with) is to create adapter objects for dealing with file attachments. The DominoDocument
class has a bit of information about attachments, particularly in its AttachmentValueHolder
inner class, but doesn't on its own handle the full job of dealing with file upload and download controls. During the processes of getting a list of files for a given field from the document and handling the "delete document" method, the XPages framework looks up appropriate factories to handle the data type.
The reason for this indirection is so that this sort of operation can work with arbitrary data: in an ideal world, the XPages framework doesn't care at all that anything it does is related to Domino, and similarly the Domino data model objects don't care at all that they're embedded in the XSP framework. By allowing the model-object author (IBM, in this case) to say "hey, when you want to get a list of file attachments for an object of this type, I can help", it decouples the operation cleanly (it's broken in this case, but the theory applies). When the framework needs to process an object, it loops through the adapter factory classes, asks each one if it can handle the object in question, and takes the adapter from the first one that returns non-null.
At first blush, this setup seems overly contrived. After all, isn't this what interfaces are for? Well, in many cases, yes, interfaces would do the job. However, sometimes it makes sense to add this extra layer of indirection. Say, for example, that you're adapting between two Java classes you don't control: you can't modify the framework to support a class you want, and you can't modify the class to conform to an interface the framework understands. In that case, an adapter factory is a perfect shim.
But in fact, I've even found it useful to adopt this structure when I control all of the code. When I was designing the model/component adapter in the frostillic.us Framework, I made the conscious decision to not tie the two sides (the controller and the model) together tightly. Instead, I wrote a pair of interfaces in the controller
package: ComponentMapAdapterFactory
and ComponentMapAdapter
. This way, when the controller gets the order to create an input field for an object's "firstName" property, it loops through the list of ComponentMapAdapterFactory
s to find one that fits. Over in the model
package, I have an appropriate factory and adapter to handle my model framework.
I could have combined these two more tightly, but I enjoy the cleanliness this brings. I may not stick with my same model framework forever, and similarly the expectations of the controller class may change; because of this separation, it's clear where tweaks will need to be made. It also gives me the freedom to use the same component-building code with model objects I don't control, such as the adapter I wrote that pulls its configuration from Notes forms.
These types of factories are not something you're likely to run into during normal XPages development, but it may be useful to bear their existence in mind. The XPages framework is a great morass of moving parts, and so being able to chart the inner workings in your mental map one bit at a time can go a long way to mastering the platform.