Search

Mobile tag

About Me

I am the "IBM Collaboration & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.
Reach out to me via:
Follow notessensei on Twitter
(posts)
Skype
Sametime
IBM
Facebook
LinkedIn
XING
Amazon Store
Amazon Kindle

Twitter

Domino Upgrade

VersionSupport end
5.0
6.0
6.5
7.0
8.0
8.5
Upgrade to 9.x now!
(see the full Lotus lifcyle) To make your upgrade a success use the Upgrade Cheat Sheet.
Contemplating to replace Notes? You have to read this! (also available on Slideshare)

Languages

Other languages on request.

Visitors

Useful Tools

Get Firefox
Use OpenDNS
The support for Windows XP has come to an end . Time to consider an alternative to move on.
StopTheSecrecy

« The "real" impact of XPages | Main| I want one - Part V »

XAgents - Web Agents XPages style

QuickImage In Domino Agents are the Swiss Army Knife of development. They can be called from the client, triggered on schedule, automatically run on an event, be associated with a web action (Open, Close) or directly called from an URL using ?OpenAgent. The ?OpenAgent use of agents is subject of this blog entry. A agent triggered from an URL can use Print statements to output results back to the browser. When the first line of your print statements is "Content-Type:..." you also can serve XML or JSON or whatever exotic format your agent logic was able to conceive. Agents come with the usual drawback: the runtime environment is intitialized new for every agent run, so you need to update a document if you want to keep track (Replication conflicts anyone?)
XPages don't have a notion of agents (you can call them in the backend, but you won't get anything back from the print statements), so it seems a step back. On closer inspection however one can see, that XPages offer a greater flexibility for the same pattern of use. Every element on an XPages has a "render" attribute which determines if the element should be rendered. "Every" includes the page itself. So when you set the page's render property to false, well the XPage engine renders nothing. So you get an empty canvas you can paint on. XPages still provides you with the page event and access to scoped variables, so you can code better performing "agents" since you can keep lookups in memory. To get the equivalent to the LotusScript print you need the ResponseWriter from the externalContext. To set the content type you use the response.setContentType also to be found in the external Context. A sample snippet you could put in the afterRenderResponse event of your page would look like this (error handling omitted):
// The external context gives access to the servlet environment
var exCon = facesContext.getExternalContext(); 

// The writer is the closest you get to a PRINT statement
// If you need to output binary data, use the stream instead
var writer = facesContext.getResponseWriter();

// The servlet's response, check the J2EE documentation what you can do
var response = exCon.getResponse();

// In this example we want to deliver xml and make sure it doesn't get cached
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");

// Here all your output will be written
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<christmas date\"24Dec\" />");

// We tell the writer we are through
writer.endDocument();
facesContext.responseComplete();
writer.close();
// Update:On 8.5.2 or later writer.close() seems not be necessary/be rather harmful. Read the revisited entry
More information about the FacesContext can be found in the Domino Developer Wiki.

Bonus Tip: Since you do not render anything from the XPage you just used, why not take that empty page and write the documentation for that XAgent?
Update: Other posts on rendering your own output in XPages: Have fun writing XAgents!

Comments

Gravatar Image1 - Few clarifications:
  1. The method described here is for rendering your own response, instead of the XPages stuff.
  2. The XPage needs to be set to "render=false".
  3. For the Writer afterRenderResponse is fine.
  4. For the Stream use beforeRenderResponse
  5. You can only have one: the writer or the stream.
  6. You can't (at least to my current knowledge) intercept the render output from XPages.
  7. "Agents XPages style" are for OUTPUT not for upload. For uploads use the XPages file upload control or (soon) the Quickr controls.
Emoticon stw

Gravatar Image2 - Hi,

I have created one XPage and have a similar form.But whatever data I am saving is not getting updated in the view after i pressed the save button in XPages.

Documents are not created.

Please share your thoughts.

Gravatar Image3 - Very cool. Will suggest this as a workaround where customer needs an intensive web agent.


Gravatar Image4 - While trying to use facesContext.getResponseStream() it returns null, but if we do
facesContext.getExternalContext().getResponse().getOutputStream()
it throws the Exception 'Can't get an OutputStream while a Writer is already in use'.

Any suggestions?

Gravatar Image5 - I have to say I don't believe this works for binary output. I get exactly the same result as Sigurbjorn.

We cannot get a binary outputstream it seems.

Can you confirm you have sent binary data? XML dont count =)

S

Gravatar Image6 - I am trying to create XML that contains HTML. I have tried every combination of cdata that I can think of.

Any thoughts. An xagent seems like a great idea, but I can not get it to work.

The same type of code using Print statement in a Lotusscript agent works fine.

Thanks Doug

Gravatar Image7 - @Simon: You can do variable declarations on server side javascript to include the type: var myvar:String = "Hello World";
You can write a java class to do your bidding (just not in the default namespace) and then do:
var x = new com.acme.outputstuff.RenderX();
return x.toString();
If you want to return binary data the writer is the wrong object. You need the stream. There the same caution like servlet programming applies: once you (or something else) opens a writer to the output you can't get a stream object anymore. So when you put code into the "afterRenderResponse" event XPages had initialized the writer, so no more access to the stream is possible. To get to the stream you need to act "earlier" and use the beforeRenderResult.
Emoticon stw

Gravatar Image8 - Very useful and powerful! I really like the native access to the servlet backend. Direct binary outupt is awesome.

Do you know if it's possible to set or manage desired charset in some way? I have tried

response.setCharacterEncoding("UTF-8");
response.setContentType("text/xml; charset=UTF-8");

request.setCharacterEncoding("UTF-8");

...but it doesn't seem to work when "echoing" variables from the querystring of a request (request.getParameter(<paramName>))


Gravatar Image9 - @Andrew: No website, no email -> no advise.
Emoticon stw

P.S.: It's mime multi-parts there are no fields in a file upload, only stream.

Gravatar Image10 - Hi Stephan

Thanks very much for the reply. Yeah I understand all that, the bit I didn't get was that the Writer had already been setup by the afterRenderResponse event.

I had actually had that idea myself but the trouble is, I can get no output from the beforeRenderResponse event using either Writer or Stream, so I had given up. Am I missing something really obvious here?

This would really make my life fun if I could get this to work =)

Cheers

Simon

Gravatar Image11 - VERY IMPORTANT: If you are using Domino 8.5.2 you must remove the last line:
writer.close();

Otherwise, you'll get "Error 500 HTTP Web Server: Command Not Handled Exception"

Gravatar Image12 - Stephan,

This got me started and was just what I was looking for, but I can't seem to include dojo.js from my server file system when render is set to false for the XPage and use it in the AfterRenderResponse event to turn a JavaScript object into JSON using dojo.toJson(). Have you tried that?

When I do it, it errors out I and I get an empty page.

Gravatar Image13 - Hi Stephan,

Great article!

Your sample did not work on 8.5.2, but the solution of @23 Don Mottolo fixes this. Maybe put this info in your article? So other people won't lose time and get a little frustrated like me, before they notice the 23th comment Emoticon

Also many thanks to Don Mottolo

Gravatar Image14 - Stephan,
Is it possible to grab the response html that has been generated by JSF and sent to the browser? I understand from your comment @5 why my attempt to use facesContext.getResponseStream won't work from the afterRenderResponse event, but is there another way?
Thank you.

Gravatar Image15 - Thanks this post is very helpful.
I'm new to xPage development and would like to use this technique to export the contents of a joined notes view to JSON. I have the view joined following Nathans tutorial, just don't know how to get it to JSON. Any ideas?

Gravatar Image16 - Interesting but do I really have to write serious code in JavaScript?

Could you use Java instead? Enough languages in Domino already and I'm very unwilling to get into writing complex code in a language that has no type checking, no variable declaration, no debugging etc.

Gravatar Image17 - Hi Stephan!
I've struggled with XPages and the request stream for several weeks now and found this entry that shows me that you might be the right guy to get an answer from Emoticon

I'm trying to create an XPage which will be called from an HTML form POST. The HTML form will contain form fields and one or multiple fileupload controls. The XPage is created with NO UI Components (thus no FileUpload control).

I can create logic in the afterRenderResponse event that outputs the field names and values posted from the HTML form, but I can't seem to find a way to get the inputstreams (or File object or what-ever) of the uploaded files.

If I output the parameters comming in from the HTML form I can output the names and values of all fields posted, and for file uploads I get the name of the file upload control and the value is something like 'com.ibm.xsp.http.UploadedFile@6cc16cc1'. I guess that somehow it should be possible to get to the InputStream or File object of each uploaded file without using Apache Commons FileUpload or the like.

Do you know of a way to get a reference to the uploaded file(s)?

Gravatar Image18 - @Meenakshi,
Presumably your agent is written well and just calls a custom class that does all the work: Just add that to the XPages classpath and use Java from JavaScript. Check the Domino designer wiki for info how to do that.

@Wright
dojo is client side JavaScript. You can't use that in SSJS. AfterRenderResponse is SSJS. Checkout the Extension Library, they have JSON support.

Gravatar Image19 - Hi Stephan

Sorry if this constitutes spam =) Bet you are sorry I found this page! I did get time to do a few tests this weekend though...

First, having discovered print()!, I tried the following fragment in all events, one at a time.

print("start");
var rs = facesContext.getResponseStream();
if(rs == null) {
print("fc.getResponseStream() == null");
} else {
print(rs.getClass().getName());
}
print("end")

This prints fce.getResponseStream() == null, in all events. I don't know if this is broken or 'working as intended'


I then tried the following.

print("start");
var ex = facesContext.getExternalContext();
var response = ex.getResponse();
var rs = response.getOutputStream();
if(rs == null) {
print("response.getOutputStream() == null");
} else {
print(rs.getClass().getName());
}
print("end")

As you predicted, the getOutputStream() fails on the afterRenderResponse() event. On all other events I see the following:

10/05/2009 19:02:25 HTTP JVM: start
10/05/2009 19:02:25 HTTP JVM: com.ibm.xsp.webapp.XspHttpServletResponse$InternalOutputStream
10/05/2009 19:02:25 HTTP JVM: end
10/05/2009 19:02:25 HTTP JVM: SEVERE: CLFAD####E: Exception thrown
10/05/2009 19:02:25 HTTP JVM: SEVERE: CLFAD####E: Exception occurred servicing request for: /test/TestCharts.nsf/test.xsp - HTTP Code: 500
10/05/2009 19:02:25 HTTP Web Server: Command Not Handled Exception [/test/TestCharts.nsf/test.xsp]

Somehow, just instantiating this object causes the XSP server to explode. I have tried flushing/closing/printing rubbish.

Feels like I'm either just one step away, or doomed forever!

Thanks for your help, appreciated.

S

Gravatar Image20 - Hi Stephan,

I Have a java agent for exporting notes doc to pdf,now I want to export the xpage into PDF.Kindly advice me how to use this existing java agent in xpages

Thank you so much


Gravatar Image21 - More spam, but I beat you to it =)

If you put code into the beforeRenderResponse event, you need to tell JSF that you have finished rendering and not to proceed to the afterRenderReponse()

cue:
facesContext.responseComplete();

Really close, thanks for the pointer!

S

Gravatar Image22 - Thanks all for your help.

Gravatar Image23 - Hi Stephan,
sorry Emoticon, here is my email and some more information about what I've tried to do:

Put Apache Commons File Upload ({ Link } into WebContent/WEB-INF/lib (and included the file in the Java build path) and then tried the following in both beforeRenderResponse and afterRenderResponse events:

var exCon = facesContext.getExternalContext();
var request:HttpServletRequest = exCon.getRequest();
var parser:com.acme.FormParser = new com.acme.FormParser();
parser.parseRequest(request);

com.acme.FormParser class is like:

package com.acme;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;


public class FormParser {


public FormParser() {
System.out.println("FormParser...");
}

public void parseRequest(HttpServletRequest request) {
System.out.println("parseRequest(request)...");
try {
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
String contentType = request.getContentType();
String characterEncoding = request.getCharacterEncoding();
System.out.println("Multipart: " + isMultipart);
System.out.println("Content type: " + contentType);
System.out.println("Character encoding: " + characterEncoding);

ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
System.out.println("We never get to this statement????");
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (item.isFormField()) {
System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected.");
} else {
System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
}
}
} catch (FileUploadException fue) {
fue.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

}

Gravatar Image24 - Hi Stephan, sorry Emoticon, here is my email and some more information about what I've tried to do:

Put Apache Commons File Upload ({ Link } into WebContent/WEB-INF/lib (and included the file in the Java build path) and then tried the following in both beforeRenderResponse and afterRenderResponse events:

var exCon = facesContext.getExternalContext();
var request:HttpServletRequest = exCon.getRequest();
var parser:com.acme.FormParser = new com.acme.FormParser();
parser.parseRequest(request);

com.acme.FormParser class is like:

package com.acme;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;


public class FormParser {


public FormParser() {
System.out.println("FormParser...");
}

public void parseRequest(HttpServletRequest request) {
System.out.println("parseRequest(request)...");
try {
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
String contentType = request.getContentType();
String characterEncoding = request.getCharacterEncoding();
System.out.println("Multipart: " + isMultipart);
System.out.println("Content type: " + contentType);
System.out.println("Character encoding: " + characterEncoding);

ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
// We never get to this statement <==================================!!!
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (item.isFormField()) {
System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected.");
} else {
System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
}
}
} catch (FileUploadException fue) {
fue.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

}

Gravatar Image25 - Hi Stephan,
I've just realized that I have submitted the above question twice. Sorry about that...
I'm still looking around for a solution or some more guidelines about how to overcome my problem regarding upload files. Hoping that you could give me some more hints as to where to look?

Disclaimer

This site is in no way affiliated, endorsed, sanctioned, supported, nor enlightened by Lotus Software nor IBM Corporation. I may be an employee, but the opinions, theories, facts, etc. presented here are my own and are in now way given in any official capacity. In short, these are my words and this is my site, not IBM's - and don't even begin to think otherwise. (Disclaimer shamelessly plugged from Rocky Oliver)
© 2003 - 2014 Stephan H. Wissel - some rights reserved as listed here: Creative Commons License
Unless otherwise labeled by its originating author, the content found on this site is made available under the terms of an Attribution/NonCommercial/ShareAlike Creative Commons License, with the exception that no rights are granted -- since they are not mine to grant -- in any logo, graphic design, trademarks or trade names of any type. Code samples and code downloads on this site are, unless otherwise labeled, made available under an Apache 2.0 license. Other license models are available on written request and written confirmation.