Search

Languages

Other languages on request.

About Me

I am the "Lotus Technology & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.

Ads by Google

Visitors

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

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();

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?

Comments

Gravatar Image1 - 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 Image2 - 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 Image3 - 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 Image4 - 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 Image5 - @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 Image6 - 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 Image7 - 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 Image8 - 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 Image9 - 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 Image10 - @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 Image11 - 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 Image12 - 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 Image13 - 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?

Gravatar Image14 - Thanks all for your help.

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::rolleyes:;-)

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 - 2009 Stephan H. Wissel - all 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.

Get Firefox Use OpenDNS