Showing posts for tag "urls"

Another Round of URL Fiddling

Thu Jun 19 17:59:51 EDT 2014

Tags: domino urls

I was working on another installment of "Be a Better Programmer", but the result isn't where I want it to be yet, so it'll have to wait. Instead, I'll share a bit about my latest futzing around when it comes to improving the URLs used in an XPages app. This has been a long-running saga, with the latest installment seeing me stumbling across a capability that had been right under my nose: the ViewHandler's ability to manipulate the URLs generated for a page. That method worked, but had the limitation that it didn't affect navigation between pages.

As it turns out, there's a slightly-better option that covers everything I've tried other than navigation rules specified in faces-config.xml (but who uses those in XPages anyway?): the RequestCustomizerFactory. This is one of the various classes buried in the Extensibility API and present in the XSP Starter Kit, but otherwise little-used. In the Starter Kit stub (and in a similar commented-out stub in the ExtLib), it can be used to add resources to a page without needing to specify them in code - say, for a plugin to add JS or CSS libraries without being specified in a theme.

However, there are other methods in the RequestParameters object that would appear to let you shim in all sorts of settings on a per-request basis - different Dojo implementations, different theme IDs, whatever "debugAgent" is, and so forth. The one that interests me at the moment is the UrlProcessor. This is aptly named: when a URL is generated by the XPage, it is passed through this processor before being sent to the browser. This appears to cover everything other than the aforementioned stock-JSF-style navigation rules: link value properties, form actions, normal navigation rules, view panel generated links, etc..

Following my previous desire, I want to make it so that all links inside my task-tracking app Milo use the "/m/*" substitution rule I set up for the server. The method for doing that via a UrlCustomizer is pretty similar to the ViewHandler:

package config;

import java.util.regex.Pattern;

import javax.faces.context.FacesContext;

import com.ibm.xsp.context.RequestCustomizerFactory;
import com.ibm.xsp.context.RequestParameters;

public class ConfigRequestCustomizerFactory extends RequestCustomizerFactory {

	@Override
	public void initializeParameters(final FacesContext facesContext, final RequestParameters parameters) {
		parameters.setUrlProcessor(ConfigUrlProcessor.instance);
	}

	public static class ConfigUrlProcessor implements RequestParameters.UrlProcessor {
		public static ConfigUrlProcessor instance = new ConfigUrlProcessor();

		public String processActionUrl(final String url) {
			String contextPath = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath();
			String pathPattern = "^" + Pattern.quote(contextPath);
			return url.replaceAll(pathPattern, "/m");
		}

		public String processGlobalUrl(final String url) {
			return url;
		}

		public String processResourceUrl(final String url) {
			String contextPath = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath();
			String pathPattern = "^" + Pattern.quote(contextPath);
			return url.replaceAll(pathPattern, "/m");
		}

	}
}

The method of adding this customizer to your app directly is, however, different: instead of being in the faces-config file, this is added as a "service", as mentioned in this handy list. For an in-NSF service, you accomplish this by adding a folder called "META-INF/services" to your app's classpath (you can add it in Code/Java) and a file in there named "com.ibm.xsp.RequestParameters":

In this file, you type the full name of your class, such as "config.ConfigRequestCustomizerFactory" from my example. You'll likely have to Clean your project after doing this to have it take effect (and possibly in between each change to the code).

Now, my current use is very limited; the UrlProcessor has free reign for its transformations and is passed every URL generated by the XPage (except hard-coded HTML, unsurprisingly) - you could change URLs like "/post.xsp?documentId=foo" to "/blog/posts/foo" without having to code that explicitly in your XPage. You could write a customizer that looks up all Substitution rules for the current Web Site from names.nsf and automatically transforms URLs to match. You could go further than that, trapping external links to process in some way in the "processGlobalUrl" method. The possibilities with the other methods of the customizer go much further (and are largely undocumented, to make them more fun), but for now just fiddling with the URLs is a nice boon.

URLs in XPages

Tue Oct 01 20:28:59 EDT 2013

Tags: urls xpages

The topic of URLs came up today in a chat and in the most recent NotesIn9, and I think this is an area that deserves some particular attention. When designing any web app, it's easy to fall into some traps with URLs, and writing XPages code juggles things around a bit - some things are much easier than usual, some are insidiously harder.

At a base level, there are four main types of URLs you'll use when writing a web app, distinguished by how they begin (don't quote me on the names, or even whether or not they're technically called "URLs"):

  • Full-protocol URLs beginning with a protocol followed by a colon, such as https://frostillic.us/f.nsf/Home.xsp
  • Protocol-relative URLs beginning with a double-forward-slash, such as //frostillic.us/f.nsf/home.xsp (these are fairly rare)
  • Server-relative URLs beginning with a forward-slash, such as /f.nsf/home.xsp
  • Page-relative URLs beginning with basically anything else, such as home.xsp

As a general rule, page-relative URLs are only a good idea if you are absolutely certain of the structure of your app relative to the URL the browser is using. The tricky part here is that it's very easy to write such a URL that works when you test it (say, pointing from one XPage to another with a URL like <xp:link value="otherXPage.xsp" text="Click me"/>), but fails in other situations. For example, see how such a URL would work when visiting each of these URLs, which all point to the same XPage:

Yes, the last one is legal - you can get to the stuff after the ".xsp" via facesContext.getExternalContext().getRequest().getPathInfo().

Fortunately, the XPage environment provides a nice solution to this problem: when using XSP elements, it converts the third type (server-relative URLs) from being relative to the server to be relative to the app. This is almost always1 what you want. So if I write a link as <xp:link value="/otherXPage.xsp" text="Click me"/>, it will work correctly in all of those situations.

Note the opening caveat: you have to be using XSP elements. Fortunately, this covers all WYSIWYG use and pretty much everything you're going to do elsewhere. It's rare that you actually want a stock-HTML <a/> instead of an <xp:link/>, and this includes resource URLs in an XPage, custom control, or theme. So, as a rule, you should almost always preface your in-app links with a forward slash to piggyback on this feature.

There are two cases that need special treatment: when you want to link out of your app from an XPage element and when you want to link into your app from a non-XPage element. Fortunately, there are two bits of non-obvious code that will do these jobs easily and reliably:

  • To break out of the app-relative "jail" in an XPage element, prefix your URL with "/.ibmxspres/domino/". For an explanation as to what this stands for and a list of similar prefix URLs, read Mastering XPages. For now, just know that that prefix means "get me out of here". So to link to, say, the directory, you could write a link like <xp:link value="/.ibmxspres/domino/names.nsf" text="Domain's Directory"/>.
  • To reliably reference the current app from a non-XPage link, use this bizarre EL formulation: ${facesContext.externalContext.requestContextPath}. What that gets you is basically like @WebDbName prefixed with a slash. So, for example, if you wanted to reference a video attached as a file resource, you could do something like <video src="${facesContext.externalContext.requestContextPath}/someVideo.mp4" />.

Keep in mind that the special XPage behavior applies only to when you're writing XSP markup (or otherwise pulling URLs into XPage elements); it doesn't apply to, for example, rich text or other HTML brought into the page.

And on a final, self-aggrandizing note, the "mentoring" offer in that NotesIn9 applies to me and my company. Feel free to contact us or get in touch with me directly if you're ever in need.

1. Except when writing CSS files. In CSS, URLs are relative to the location of the CSS file. This is usually just fine, since you, not the browser, control the URLs you use to refrence the CSS file. It gets tricky when you use resource aggregation, though.

Taking a Swing at the URL Problem

Sun Nov 04 19:20:03 EST 2012

Tags: xpages urls

In the new portfolio web site I'm setting up for myself, I've decided to see what I can do about Domino's, and XPages' specifically, tendency towards ungainly URLs. An XPage URL, particularly an auto-generated one from, say, a view panel, can quickly become filled with undesirable elements - namely, ".nsf", ".xsp", "$$OpenDocument", "action=", and "documentId=". They all make sense and serve important purposes for the server, and to a certain extent URLs other than the main one don't matter, but I want a clean address bar, dang it.

The approach I'm trying now involves creating a managed bean named "url" that implements Map<String, String>. Its get() method takes a normal URL of the kind that you would usually write for a page and returns a cleaned-up version if it can find it, and the original URL otherwise. So a link to, say, the contact page looks like this:

<xp:link text="Contact" value="${url['/Contact.xsp']}"/>

I also set up a "link" custom control to act as a drop-in replacement so I don't have to do that EL bit everywhere. The code itself generates an internal map based on a view of simple From/To "Alias" documents in the DB, though I'm considering having it peek into names.nsf to find any applicable Web Rules more dynamically. The net effect is that I can use that bit of EL or the custom control and hand it a "normal"-style database-relative XPage URL and it'll clean it up if it can or, worst case, pass it through directly.

I can imagine some improvements, beyond the switch to looking at names.nsf instead. For one, I should make it handle "partial" replacements, so that I could map, say "/whatever.nsf/Posts/postid" to just "/posts/postid". I could go beyond that, too, making it do regular expressions to allow it to translate arbitrarily-complex routes beyond what just checking Web Rules could do.

I'm not certain that this is necessarily the best way (the REALLY best way would be to hook into whatever code handles all URL generation in the app), but it has a nice simplicity to it and I figure it's worth a shot.