XAgents - Web Agents XPages style
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 entryMore 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
Posted by Stephan H. Wissel At 02:26:22 On 03/03/2010 | - Website - |
Posted by Simon O'Doherty At 18:57:43 On 02/28/2010 | - Website - |
facesContext.getExternalContext().getResponse().getOutputStream()
it throws the Exception 'Can't get an OutputStream while a Writer is already in use'.
Any suggestions?
Posted by Sigurbjorn At 23:15:33 On 01/28/2009 | - Website - |
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.
Posted by Meenakshi At 18:59:53 On 03/02/2010 | - Website - |
We cannot get a binary outputstream it seems.
Can you confirm you have sent binary data? XML dont count =)
S
Posted by Simon Mottram At 10:38:54 On 05/06/2009 | - Website - |
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>))
Posted by Fredrik Stöckel At 19:31:52 On 01/07/2009 | - Website - |
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.
Posted by Stephan H. Wissel At 10:33:36 On 05/08/2009 | - Website - |
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
Posted by Doug At 06:34:50 On 12/09/2010 | - Website - |
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.
Posted by Wright Furman At 08:04:17 On 12/02/2010 | - Website - |
writer.close();
Otherwise, you'll get "Error 500 HTTP Web Server: Command Not Handled Exception"
Posted by Don Mottolo At 20:18:47 On 12/03/2010 | - Website - |
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
Posted by Simon At 00:02:24 On 05/09/2009 | - Website - |
P.S.: It's mime multi-parts there are no fields in a file upload, only stream.
Posted by Stephan H. Wissel At 03:52:53 On 05/19/2009 | - Website - |
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
Also many thanks to Don Mottolo
Posted by Philippe At 21:59:48 On 12/29/2010 | - Website - |
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?
Posted by Joe Randel At 09:07:03 On 10/31/2009 | - Website - |
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.
Posted by Todd Harris At 02:15:34 On 03/03/2010 | - Website - |
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
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)?
Posted by Andrew Tjecklowsky At 09:56:50 On 05/15/2009 | - Website - |
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.
Posted by simon mottram At 07:14:46 On 03/06/2009 | - Website - |
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.
Posted by Stephan H. Wissel At 17:39:50 On 12/02/2010 | - Website - |
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
Posted by Simon At 17:20:39 On 05/10/2009 | - Website - |
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
Posted by Meenakshi At 16:12:57 On 08/27/2010 | - Website - |
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
Posted by Simon At 21:52:33 On 05/11/2009 | - Website - |
Posted by Edwin At 05:50:51 On 06/07/2009 | - Website - |
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?
Posted by Andrew Tjecklowsky At 05:10:54 On 06/02/2009 | - Website - |
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();
}
}
}
Posted by Andrew Tjecklowsky At 18:35:40 On 05/21/2009 | - Website - |
sorry
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();
}
}
}
Posted by Andrew Tjecklowsky At 18:38:44 On 05/21/2009 | - Website - |