Blog

Scheduling XPages Code – Part Four

  |   Blog   |   No comment

In the previous part I covered Node-RED. The announcement a few weeks ago of NodeJS integration with Domino will hopefully reduce resistance from customers for using the two together. In this part I’ll start covering XPages code triggered from the scheduled tasks – or external integration points – of Node-RED. Bear in mind that I’m starting from the most basic, manual example and will move towards the more involved, flexible and advanced example. But in actual fact, this most complex example also means the least code for developers and can leverage the advantages of Java 8.

XPages UI Action

Before starting, it’s important to look at the ContactsView XPage. This has a DataView which allows selection of documents and has a button to archive the selected documents. Yes, there are easier and better ways to archive, but this is about having XPages code used from the UI and from a scheduled task, and moving documents to another database was a simple example. I’m sure you can think of places where you’ve wanted to reuse code or will come across places where you want to. The button calls com.paulwithers.Utils.archiveSelected().

public static void archiveSelected(String[] ids) {
	try {
		for (String id : ids) {
				Database db = Factory.getSession(SessionType.CURRENT).getCurrentDatabase();
			Document doc = db.getDocumentByID(id);
			if (!archiveDoc(doc)) {
				System.out.println("FAILED!");
			} else {
				doc.remove(true);
			}
		}
	} catch (Throwable t) {
		t.printStackTrace();
	}
}

To call a method in that class without creating a Utils object first, the method needs to be public (available outside the class) and static (can be called without an object being created from the class). This method calls archiveDoc() which just copies the document to the archive database and removes it – pretty straightforward.

public static boolean archiveDoc(Document doc) {
	try {
		System.out.println("archiving doc " + doc.getUniversalID());
		Database archDb = Factory.getSession(SessionType.CURRENT).getDatabase(ARCHIVE_DB_PATH);
		Document archDoc = archDb.createDocument();
		doc.copyAllItems(archDoc, true);
		System.out.println("archived doc " + doc.getUniversalID());
		return archDoc.save();
	} catch (Throwable t) {
		t.printStackTrace();
		return false;
	}
}

Basic Scheduled Task

The first example is an XAgent calling a Java method. The Java method kicks off a Xots task and returns a JSON response.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" viewState="nostate"
	xmlns:xe="http://www.ibm.com/xsp/coreex">
	<xp:this.beforeRenderResponse><![CDATA[#{javascript:com.paulwithers.Utils.processBackgroundTask()}]]></xp:this.beforeRenderResponse>
</xp:view>

The XAgent SchedArchive.xsp is, as all XAgents should be, set with viewState="nostate". The Java method is called in beforeRenderResponse, com.
paulwithers.Utils.processBackgroundTask()
.

public static void processBackgroundTask() {
	try {
		FacesContext ctx = FacesContext.getCurrentInstance();
		ExternalContext ext = ctx.getExternalContext();
		XspHttpServletResponse response = (XspHttpServletResponse) ext.getResponse();
		response.setContentType("application/json");
		response.setHeader("Cache-Control", "no-cache");
		PrintWriter writer = response.getWriter();
		Xots.getService().submit(new SchedTask());
		// This line highlights why you should use a JsonWriter - painful and prone to error
		writer.write("{\"message\": \"asynchronous task running\"}");

		//  Terminate the request processing lifecycle.
		FacesContext.getCurrentInstance().responseComplete();
	} catch (Throwable t) {
		t.printStackTrace();
	}
}

That method gets the XspHttpServletResponse, sets content type as JSON and sets a header (other headers could be set). After triggering the Xots task JSON is written manually, the same as you would with a print statement in a LotusScript agent and the response marked complete. Obviously this is a painful way of constructing a JSON response and one that is prine to error, but it shows the most basic version.

The Xots task itself is com.paulwithers.xots.SchedTask. For those not used to Xots, the class extends AbstractXotsXspRunnable which means it’s a background task (Runnable) which gets loaded with a XotsContext object which has access to the current session and database and can log to OpenLog. All the code for a Xots Runnable goes into the run() method which all Runnables have. The key part is the call to Utils.archiveDoc(doc) which is a generic method also used from our XPage for archiving.

Congratulations! We’ve now created a scheduled task which reuses code called from an XPage. However, this could be called from a GET, POST, PUT or DELETE. And constructing JSON manually is not ideal. We can do better.

SmartNSF Example

The second example I want to cover is using SmartNSF.

router.GET('xrestArchive') {
	strategy(CUSTOM) {
		javaClass('com.paulwithers.CustomRest')
	}
}

This uses an “xrestArchive” endpoint. In order to trigger a Xots task we need to use the CUSTOM strategy. The code I’ve used is not very different from the example in the documentation. It uses a Java class com.paulwithers.CustomRest.

public class CustomRest implements CustomRestHandler {

	/* (non-Javadoc)
	 * @see org.openntf.xrest.xsp.exec.CustomRestHandler#processCall(org.openntf.xrest.xsp.exec.Context, java.lang.String)
	 */
	public void processCall(Context context, String path) throws Exception {
		// Schedule the Xots task. Note: we can't re-use the XPages one, there are differences in how SmartNSF works
		Xots.getService().submit(new SchedTaskNonXSP());
		// The rest is pretty standard for SmartNSF
		JsonJavaObject result = new JsonJavaObject();
		result.put("message", "asynchronous xrest task scheduled");
		JsonWriter jsw = new JsonWriter(context.getResponse().getWriter(), true);
		jsw.outObject(result);
		jsw.close();
		// One difference, we'll set the response code to 202 - a request was accepted for processing, but was not completed
		context.getResponse().setStatus(HttpServletResponse.SC_ACCEPTED);
	}

}

As with the documentation, this needs to implement the CustomRestHandler Java interface and have a processCall() method. As with the SmartNSF example, it uses JsonWriter to output JSON from a JsonJavaObject – it’s like a Map which you’re familiar with from scoped variables. The one difference is that the response code status is set to ACCEPTED, code 202, because we’ve received and validated the request but passed it off for background processing. So we’re not certain it’s successful, so we shouldn’t give a 200 response.

The other difference is we’re triggering a different Xots task, com.paulwithers.xots.SchedTaskNonXSP. This is because SmartNSF doesn’t have access to all of the XPages runtime. So it needs to extend AbstractXotsRunnable. Other than that the code is the same.

In the next part, we’ll look at making it even easier and giving a generic REST endpoint that can be used as well.

AUTHOR - Paul Withers

Paul Withers is an IBM Lifetime Champion, has been an OpenNTF Board Member since 2013, has worked with Domino since R4.5, XPages since 2009, co-authored XPages Extension Library and was technical editor for Mastering XPages 2nd Edition. He is one of the developers on OpenNTF Domino API as well as contributor to a variety of other OpenNTF projects. For full bio, see https://paulswithers.github.io/professional/

No Comments

Post A Comment