Using the ODA Design API for File-Resource Manipulation

Wed Nov 19 18:25:15 EST 2014

Tags: oda design-api

As is characteristic of his blog, Sven Hasselbach recently posted two interesting posts on using the NAPI classes in the XPages runtime to manipulate files in the WebContent folder. If you haven't read the posts, I suggest you do so now, because it's knowledge that is very good to have. The NAPI classes are chock full of cheating sorcery.

But the point of this post here is a bit of me-too-ism. Which is to say: the OpenNTF Domino API has you covered on this front. The API's Design package (org.openntf.domino.design.*) contains classes that, among other things, let you retrieve, create, and modify both "normal" and these extra file resources.

The starting point is the getDesign() method on an ODA database object. Using this DatabaseDesign object, you can get access to files. For example:

Database database = FrameworkUtils.getDatabase();

DatabaseDesign design = database.getDesign();

for(FileResource res : design.getFileResources()) {
	// do stuff
}

FileResource newResource = design.createFileResource();
newResource.setName("some file.txt");
newResource.setFileData("some content".getBytes());
newResource.save();

FileResource existing = design.getAnyFileResource("existing-file.txt");
String existingContent = new String(existing.getFileData());
existing.setFileData((existingContent + " new stuff").getBytes());
existing.save();

Created file resources show up in the File Resources section currently (you can call setHideFromDesignList(true), which removes them from there, but puts them at the root of the VFS... that works, but it'd be better for them to show up in WebContent (putting "WebContent/" in the name LOOKS like it works, but doesn't)). The getAnyFileResource method will search for any "file" type element with the given name - normal file resources, WebContent files, XPages, and Java source/class files. There are better classes for manipulating the latter two, but they show up because that's how they're implemented.

One note: the Design API uses DXL, not the NAPI classes, which means it gains freedom from an XSP dependency at the cost of memory efficiency. To do its thing, it must export the file as DXL, which will be larger in memory than the file size, and then convert that BASE64 representaiton to bytes, meaning the memory cost is more than double the size of the file. For most files, that's fine, but be wary of dealing with, say, 200MB video files. That sort of thing totally works (I tested), but only if you have the memory to spare and your JVM configured to use it. Maybe one day the API will have direct access to the notes.

Okay, a second note: it's important to make sure that the max Internet access level is Designer or above for this to work, at least with the normal session object.

If you haven't taken a look at the Design API before, it may be worth at least glancing over the DatabaseDesign interface to get a feel for what it currently allows. Maybe attention on it will coax me into finding free time to finish all the other aspects I plan to get to.

Factories in XPages

Wed Nov 12 18:27:14 EST 2014

Tags: java xpages

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 ComponentMapAdapterFactorys 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.

Property Resolution in XPages EL

Tue Nov 11 11:24:21 EST 2014

Tags: xpages java

Reading Devin Olson's recent series on EL processing put me in the mood to refresh and fill out my knowledge of how that stuff actually happens.

A while back, I made a small foray of my own into explaining how property resolution in XPages EL works, one which I followed up with a mea culpa explaining that I had left out a few additional supported types. As happens frequently, that still didn't cover the full story. Before getting to what I mean by that, I'll step back to an overview of XPages EL in general.

Components of EL Processing

To my knowledge, there are three main conceptual pieces to the EL-resolution process. I'll use the EL #{foo.bar.baz[1]} as a common example.

  • The EL parser itself. This is what reads the EL above and determines that you want the 1-indexed property of the baz property of the bar property of the foo object. I don't know if there's a realistic way to override or extend this stock-EL behavior.
    • This does, though contain an extensible side path: BindingFactory. This lets you create your own processors for value and method bindings based on the EL prefix, in the same vein as #{javascript: ... }.
  • The VariableResolver. This is a relatively-common bit of XPages extensibility and for good reason: they're quite useful. The variable resolver is what is used by EL (and SSJS, and others) to determine what object is referenced by foo in the example.
  • The PropertyResolver. This is the companion to the VariableResolver and is what handles the rest of the dereferencing in the example. EL asks the app's property resolver to find the bar property of foo, then the baz property of that, and then the 1 indexed property of that. This is the main topic of conversation today.

Setting An Application's PropertyResolver

There are two main ways I know of to make use of property resolvers, and the first is analogous to the VariableResolver: you write a single object and specify it in your faces-config file, like so:

<application>
	<property-resolver>config.PropResolver</property-resolver>
</application>

Then the skeletal implementation of such an object looks like this:

package config;

import javax.faces.el.EvaluationException;
import javax.faces.el.PropertyNotFoundException;
import javax.faces.el.PropertyResolver;

public class PropResolver extends PropertyResolver {
	private final PropertyResolver delegate_;

	public PropResolver(PropertyResolver delegate) {
		delegate_ = delegate;
	}

	@Override
	public Class<?> getType(Object obj, Object property) throws EvaluationException, PropertyNotFoundException {
		return delegate_.getType(obj, property);
	}

	@Override
	public Class<?> getType(Object obj, int index) throws EvaluationException, PropertyNotFoundException {
		return delegate_.getType(obj, index);
	}

	@Override
	public Object getValue(Object obj, Object property) throws EvaluationException, PropertyNotFoundException {
		return delegate_.getValue(obj, property);
	}

	@Override
	public Object getValue(Object obj, int index) throws EvaluationException, PropertyNotFoundException {
		return delegate_.getValue(obj, index);
	}

	@Override
	public boolean isReadOnly(Object obj, Object property) throws EvaluationException, PropertyNotFoundException {
		return delegate_.isReadOnly(obj, property);
	}

	@Override
	public boolean isReadOnly(Object obj, int index) throws EvaluationException, PropertyNotFoundException {
		return delegate_.isReadOnly(obj, index);
	}

	@Override
	public void setValue(Object obj, Object property, Object value) throws EvaluationException, PropertyNotFoundException {
		delegate_.setValue(obj, property, value);
	}

	@Override
	public void setValue(Object obj, int index, Object value) throws EvaluationException, PropertyNotFoundException {
		delegate_.setValue(obj, index, value);
	}
}

If you've done much with DataObjects, you'll likely immediately recognize those methods: I imagine that DataObject was created as a simplest-possible implementation of a PropertyResolver-friendly interface.

So how might you use this? Well, most of the time, you probably shouldn't - in my experience, the standard property resolver is sufficient and using DataObject in custom objects is easy enough that it's the best path. Still, you could use this to patch the behavior that is driving Devin to madness or to paint over other persistent annoyances. For example, DominoViewEntry contains hard-coded properties for accessing the entry's Note ID, but not its cluster-friendly Universal ID. To fix this, you could override the non-indexed getValue method like so:

public Object getValue(Object obj, Object property) throws EvaluationException, PropertyNotFoundException {
	if (obj instanceof DominoViewEntry && "documentId".equals(property)) {
		return ((DominoViewEntry) obj).getUniversalID();
	}
	return delegate_.getValue(obj, property);
}

Now, anywhere where you have a DominoViewEntry, you can use #{viewEntry.documentId} to get the UNID. You could do the same for DominoDocument as well, if you were so inclined. You'll just have to plan to never have a column or field named "documentId" (much like you currently have to avoid "openPageURL", "columnIndentLevel", "childCount", "noteID", "selected", "responseLevel", "responseCount", and "id").

Property Resolver Factories

The other way to use PropertyResolvers is to register and use a PropertyResolverFactory. Unlike the faces-config approach, these do not override (all of) the default behavior, but are instead looked up by IBM's PropertyResolver implementation at a point during its attempts at property resolution. Specifically, that point is after support for ResourceBundle, ViewRowData, and DataObject and before delegation to Sun's stock resolver (which handles Maps, Lists, arrays, and generic POJOs).

If you get a type hierarchy, you can see that IBM uses this route for lotus.domino.Document, com.ibm.commons.util.io.json.JsonObject, and com.ibm.jscript.types.FBSObject (SSJS object) support. So the idea of this route is that you'd have your own custom object type which doesn't implement any of the aforementioned interfaces and for which you want to provide EL support beyond the normal getter/setter support. Normally, this is not something worth doing, but I could see it being useful if you have a third-party class you want to work in, such as a non-Domino/JDBC data source.

The method for actually using one of these is... counterintuitive, but is something you may have run into in plugin development. The first step is simple enough: implement the factory:

package config;

import javax.faces.el.PropertyResolver;
import com.ibm.xsp.el.PropertyResolverFactory;

public class PropResolverFactory implements PropertyResolverFactory {
	public PropertyResolver getPropertyResolver(Object obj) {
		return null;
	}
}

That getPropertyResolver method's job is to check to see if the object is one of the types it supports and, if it is, return a PropertyResolver object (the same kind as above) that will allow the primary resolver to get the property.

Actually registering the factory is weirder. It must be done via a plugin (or by code that manually registers it in the application when needed), and the best way to see what I mean is to take a look at an example: the OpenntfDominoXspContributor class used by the OpenNTF Domino API. The contributor is registered in the plugin.xml and returns an array of arrays (because Java doesn't have tuples or map literals) representing a unique name for your factory plus the implementing class.

This concept of factories actually probably warrants its own blog post down the line. For the time being, the upshot is that this approach is appropriate if you're adding your own data type via a plugin (and which wouldn't be better-suited to implement DataObject), so it's a rare use case indeed.

Using "Verboten" Property Names in Custom Controls

Sun Nov 02 09:46:28 EST 2014

Tags: xpages

In an attempt to save you from yourself, Designer prevents you from naming your custom control properties after SSJS keywords such as "do" and "for". This is presumably because a construct like compositeData.for would throw both a syntax error in SSJS and the developer into a tizzy. However, sometimes you want to use one of those names - they're not illegal in EL, after all, and even SSJS could still use compositeData['for'] or compositeData.get("for") to access the value.

Fortunately, this is possible: if you go to the Package Explorer view in Designer and open up the "CustomControls" folder of your NSF, you'll see each custom control as a pair of files: an ".xsp" file representing the control markup and an ".xsp-config" file representing the metadata specified in the properties pane, including the custom properties. Assuming you attempted to type "for" for the property name and were stuck with "fo", you'll see a block like this:

<property>
	<property-name>fo</property-name>
	<property-class>string</property-class>
</property>

Change that "fo" to "for" and save and all is well. You'll be able to use the property just like you'd expect with a normal property, with the caveat above about how to access it if you use SSJS. I wouldn't make a habit of using certain keywords, such as "class", but "for" is perfectly fine and allows your controls to match stock controls such as xp:pager.

This came up for me in one of the controls I like to keep around when dealing with custom renderers: a rendererInfo control to display some relevant information. Since I keep forgetting where I last used such a control, I figure I should post it here partially for my own future reference.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
	<table>
		<tr>
			<th>Client ID</th>
			<td><xp:text><xp:this.value><![CDATA[#{javascript:
				var comp = getComponent(compositeData['for']);
				return comp == null ? 'null' : comp.getClientId(facesContext);
			}]]></xp:this.value></xp:text></td>
		</tr>
		<tr>
			<th>Theme Family</th>
			<td><xp:text><xp:this.value><![CDATA[#{javascript:
				var comp = getComponent(compositeData['for']);
				return comp == null ? 'null' : comp.getStyleKitFamily();
			}]]></xp:this.value></xp:text></td>
		</tr>
		<tr>
			<th>Component Family</th>
			<td><xp:text><xp:this.value><![CDATA[#{javascript:
				var comp = getComponent(compositeData['for']);
				return comp == null ? 'null' : comp.getFamily();
			}]]></xp:this.value></xp:text></td>
		</tr>
		<tr>
			<th>Renderer Type</th>
			<td><xp:text><xp:this.value><![CDATA[#{javascript:
				var comp = getComponent(compositeData['for']);
				return comp == null ? 'null' : comp.getRendererType();
			}]]></xp:this.value></xp:text></td>
		</tr>
		<tr>
			<th>Renderer Class</th>
			<td><xp:text><xp:this.value><![CDATA[#{javascript:
				var comp = getComponent(compositeData['for']);
				var renderer = comp == null ? null : comp.getRenderer(facesContext);
				return renderer != null ? renderer.getWrapped().getClass().getName() : 'N/A'
			}]]></xp:this.value></xp:text></td>
		</tr>
	</table>
</xp:view>

CocoaLove Reflection

Sun Oct 26 20:13:10 EDT 2014

Tags: cocoa

This weekend, I attended CocoaLove, a new Mac/iOS-development-related conference held in Philadelphia. Though my Cocoa resume consists of doing various tutorials every few years for the last decade or so, the location, concept, and speaker lineup were impossible to resist.

The upshot: this was a great conference. As the tagline – "A conference about people, not tech." – indicates, the sessions weren't technical or even generally about programming as such. Instead, it was a bit more in the ATLUG Day of Champions vein. They covered a range of useful "surrounding" topics, from self-image, to lessons from other industries, to diversity (in a far more interesting sense than that semi-buzzword makes it sound). The secondary push of the conference was social-in-the-sense-of-socializing - the keynote encouraged everyone to introduce themselves and the tables were stocked with levels-of-introversion pins, something that could be a silly conceit but worked well.

In fact, the socializing push worked remarkably well, thanks in large part to the nature of the talks. Since it was a single-track conference and the topics weren't technical reference material, laptops were almost entirely sheathed the whole time and even phone-checking was shockingly limited. Since the event was in a single room, there was no walking around needed between sessions - the breaks were spent talking about the just-presented topic or getting to know the people sitting with you.

This was also personally a very interesting experience for me. When it comes to Cocoa development, I am but an egg. It was weird being back in the position of not being known by anyone and only knowing a few people by their works and reputation – it was like my first MWLUG a couple years ago. I had a bit of "I got to meet Marco Arment and Brent Simmons!" fanboy-ism, but mostly it was great meeting a whole slew of people in a community I've only ever observed from the outside. It also made me realize that I need to get over the hump of the train ride and watch for more events in the city.

For reference, as you'd probably expect, nobody had any idea what "IBM Domino" is other than one long-former IBMer. The reactions I got when I explained that I do Java development all day ranged from "ah, I've used that for some Android development" to the sort of sympathetic reaction you'd get if you told someone you were just evicted from your house.

On a final note, the conference badges were amazing. They were all hand-drawn renditions of attendees' Twitter-or-otherwise avatars and it was an unexpected cool touch. The Fracture (one of the sponsors) prints they threw in were a nice bonus.

A Welcome SSL Stay of Execution

Tue Oct 21 17:52:58 EDT 2014

Tags: ssl

As you likely know from the torrent of posts on Planet Lotus on the topic, IBM announced a hopefully-imminent pair of updates to cover the two main SSL issues that have come to the fore recently: lack of SHA-2 support and the POODLE vulnerability in SSLv3. This is welcome indeed!

Personally, I'm going to stick with the nginx approach for HTTP, even in simple setups, because I've found the extra features you can get (and the promising new ones I haven't tried) to be a dramatic improvement in my server's capabilities. But in the mean time, I'm pleased that the pressure to investigate proxies for other protocols is lessened for the time being. It's not a full SSL revamp (the technote only mentions TLS 1.0 for Domino), but it's something to calm the nerves.

Nonetheless, it's been a good experience to branch out into better ways of running the server. I expect I'll eventually look into mail and LDAP proxying, both to get the highest level of SSL security and to see how useful the other features are (mail load balancing and failover, in particular, would be welcome in my setup).

Some Notes on Developing with the frostillic.us Framework

Thu Oct 09 19:23:04 EDT 2014

Tags: framework

Now that I have a few apps under my belt, I've been getting a better idea of the plusses and minuses of my current development techniques - the frostillic.us Framework combined with stock controls + renderers. This post is basically a mostly-unordered list of my overall thoughts on the current state.

  • Component binding is absolutely the way to go. This pays off in a number of ways, but just knowing that the component is pointed unambiguously at a model property - and thus getting its field type and validators from there - feels right.
  • Similarly, externalizing all strings for translation via a bean or via component binding is definitely the way to go. The "standard" way of adding translation promises the ability to not have to think about it until you're ready, but the result is more of a drag.
  • On the other hand, having to manually write out translation lines for every model property and enum value (e.g. model.Task$TaskStatus.INPROGRESS=In Progress) is a huge PITA. Eventually, it may be worth writing a tool to look for model objects in a DB and present a UI for specifying translations for each property and enum value.
  • It feels like there's still too much domain knowledge required for using Framework objects. Though I try to stick with standard Java and XSP idioms as much as possible, you still have to "just know" and remember classes like BasicXPageController, AbstractDominoModel (and that you should make a AbstractDominoManager inside it), and AbstractXSPServlet. This may be largely unavoidable - Java isn't big on implied and generated code without work. But there's enough specialized knowledge that even I've forgotten stuff like how I added support for the @Table annotation for models to set the form.
    • Designer plugins could help with this, providing ways to create each class type with pre-made Java templates and potentially adding views of each class type. I don't know if I want to bother doing that, though.
  • @ManagedBean is awesome and makes the faces-config.xml method seem archaic (even in light of my recent dabbling with the editor). The down side I can think of is that you don't have a good overview of what the beans in your app are, but that doesn't really come up as a need in reality.
  • The Framework and renderers work great in XPiNC. Good to know, I suppose. They are probably actually a huge boost, speed-wise, over putting a lot of code and resources in the NSF - they dramatically cut down on the network transactions required to render a page.
  • Sticking with stock+ExtLib controls is mostly great. Combined with component binding, my XPages are svelte, I have comparatively few custom controls, and I haven't had to go through the laborious process of writing controls in Java.
  • On the other hand:
    • Trying to write a Bootstrap app with standard components leaks like a sieve. Though there are many times when the controls match up perfectly - xe:formTable, xe:forumView, etc. - the stock controls have no concept of Bootstrap's column layout, so I've had to write custom controls for that.
    • Some ExtLib controls are surprisingly rigid, lacking attrs properties or having weird interaction models like the onItemClick event with context.submittedValue on trees. I guess I could add them myself, but I don't want to get into the business of maintaining a forked ExtLib.
    • Trying to adapt standard code to Bootstrap+jQuery can be a huge PITA. For example, Select2 (and Chosen) don't trigger onchange event handlers written in XSP. While there are ways to work around it, they involve writing odd code that makes big assumptions about the final rendering, and avoiding that is the whole point. I have a "fix" that sort of works, but it's not ideal - it has the side effect of triggering a too-much-recursion exception on the console. There's a bunch of this sort of thing to deal with.
  • My model framework has been serving me extremely well, but some aspects feel weirder over time (like how it blurs the distinction between getting a collection vs. individual model by key). I'm still considering switching to Hibernate OGM, but adapting it would likely be a mountain of work and I'm not 100% sold on its model. Still, the idea of moving to a "real" framework is appealing.
  • Using enums for fixed-value-set model properties is great.
  • I don't yet have a good solution for multi-lingual data. Maybe I could use a convention like "fieldname$fr". It hasn't actually cropped up, though, so it's a theoretical issue.
  • I should standardize the way I do configuration and come up with a standard "keywords" mechanism.
  • Similarly, I should codify the bean-backed table into a control, since I use this all the time and end up with similar code all over the place.
  • I should add specific support for using model objects as properties on other objects - both referenced by ID and potentially beans stored via MIME in the documents.
  • I need to add a way to specify error messages in the model translation file. Currently it's not much better than this.
  • I should really add built-in Excel exporting for collections.
  • I'm going to be happy I did the REST services down the line.

Overall, it mostly feels right, and working on "normal" apps feels archaic and brittle by comparison.

Building an App with the frostillic.us Framework, Part 7

Tue Oct 07 21:00:41 EDT 2014

  1. Building an App with the frostillic.us Framework, Part 1
  2. Building an App with the frostillic.us Framework, Part 2
  3. Building an App with the frostillic.us Framework, Part 3
  4. Building an App with the frostillic.us Framework, Part 4
  5. Building an App with the frostillic.us Framework, Part 5
  6. Building an App with the frostillic.us Framework, Part 6
  7. Building an App with the frostillic.us Framework, Part 7

Well, it's been much longer than planned, and this topic isn't actually particularly groundbreaking, but the series returns!

  1. Define the data model
  2. Create the view and add it to an XPage
  3. Create the editing page
  4. Add validation and translation to the model
  5. Add notification to the model
  6. Add sorting to the view
  7. Basic servlet
  8. REST with Angular.js

One of the edge features of the Framework is that it assists in writing DesignerFacesServlet servlets - which are sort of like XAgents but written directly as Java classes, without an XPage component.

Before I explain how they work in the Framework, there's a caveat: these servlets do not have (reliable) sessionAsSigner access. The reason for this is that IBM's mechanism for determining the signer doesn't cover the case of just having a Java class. That said, it does have access to the rest of the XPages environment, including the same instances of managed beans available to XPages.

With that unpleasantness aside, here's an example servlet:

package servlet;

import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import frostillicus.xsp.servlet.AbstractXSPServlet;

public class ExampleServlet extends AbstractXSPServlet {
	@Override
	protected void doService(HttpServletRequest req, HttpServletResponse res, FacesContext facesContext, ServletOutputStream out) throws Exception {
		out.println("hello");
	}
}

Once you create that class (in the package "servlet"), it is available as "/foo.nsf/xsp/exampleServlet". As with an XPage, you can add arbitrary stuff after the servlet name with a "/" and in the query string. Unlike in an XPage, the servlet name is not removed from the path info. So, for example, this method:

protected void doService(HttpServletRequest req, HttpServletResponse res, FacesContext facesContext, ServletOutputStream out) throws Exception {
	Map<String, String> param = (Map<String, String>) ExtLibUtil.resolveVariable(facesContext, "param");
	out.println("param: " + param);
	out.println("pathInfo: " + req.getPathInfo());
}

...with this URL fragment:

foo.nsf/xsp/exampleServlet/foo?bar=baz
...results in this in the browser:
param: {bar=baz}
pathInfo: /xsp/exampleServlet/foo

By default, the result is served as text/plain, but you can change that as usual, with res.setContentType(...).

For most apps, a servlet like this isn't necessary. And for apps that do have a use for servlets, the XAgent and ExtLib-control routes may be more useful. Nonetheless, I've found a number of uses for these, and I appreciate that I don't have a bunch of extra non-UI XPages cluttering up the list.

NotesIn9 Appearance: Custom Renderers

Thu Oct 02 21:16:20 EDT 2014

In a bout of unintentional timing, Dave Leedy posted an episode of NotesIn9 I recorded about custom renderers. This should pair nicely with my last post - the video provides an explanation for what renderers are, how to attach them to your components, and an example of a basic renderer for a widget container. So if you're interested in going down that path (which you should be!), perhaps the video will help.

In it, I recommend looking at the source of Bootstrap4XPages, which remains an excellent source, and now my own renderers may prove useful as well. Once you have a good handle on how they work, the layout renderer may be a good resource.

I Posted My WrapBootstrap Ace Renderkit

Tue Sep 30 19:49:46 EDT 2014

Tags: bootstrap

Since I realized there was no reason not to and it could be potentially useful to others, I tossed the renderkit I use for the WrapBootstrap Ace theme up on GitHub:

https://github.com/jesse-gallagher/Miscellany

As implied by the fact that it's not even a top-level project in my own GitHub profile, there are caveats:

  • The theme itself is not actually included. That's licensed stuff and you'd have to buy it yourself if you want to use it. Fortunately, it's dirt cheap for normal use.
  • It's just a pair of Eclipse projects, the plugin and a feature. To use it, you'll have to import them into Eclipse (or Designer, probably) with an appropriate plugin development environment, copy in the files from the Ace theme to the right place, export the feature project, and add it to Designer and Domino, presumably through update sites
  • Since it's not currently actually extending Bootstrap4XPages (though I did "borrow" a ton of the code, where attributed), it may not cover all of the same components that that project does.
  • I make no guarantees about maintaining this forked version, since the "real" one with the assets included is in a private repository.
  • I haven't added the theme to the xsp.properties editor via the handy new ability IBM added to the ExtLib yet. You'll have to name it manually, as "wrapbootstrap-ace-1.3" with "-skin1", "-skin2", and "-skin3" suffix variants.

Still, I figured it may be worthwhile both as a plugin directly and as an educational endeavor. I believe I cover a couple bases that Bootstrap4XPages doesn't and, since I'm writing for a predefined theme with a specific set of plugins and for my own needs, I was able to make more assumptions about how things should work. Some of those are a bit counter-intuitive (for example, a "bare word" image property on the layout linksbar (like image="dashboard") actually causes the theme to render a Font Awesome icon with that name), but mostly things should work like you'd expect. The theme contains no controls of its own.

So... have fun with it!