A Legacy Notes Developer's journey into madness.

Scheduled XAgents

Devin Olson  June 25 2015 10:39:50 AM
Using scheduled agents that can access the XPages runtime would be awesome, would it not?  

If you, like me, have built up a library of way cool Java API stuff that runs in XPages, you may have noticed one frustrating little problem.  You cannot access the JSF / XPages runtime stuff from a scheduled agent.

The OpenNTF Domino API (ODA) has received a lot of unfair blame about this, much of which has come from me.   The OpenNTF Domino API (ODA) is truly awesome.  If you are building XPages applications, you need to use it.  

However, it has one small little issue.  

You see, it really should not be called Domino API -because that implies it works across all of Domino, and that really is not the case. It is designed to work within the JSF / XPages architecture of your Domino server, and as such it requires that those things be present in order to work.

Now, this really is not that big of a deal, until we try to run code on the Domino server in a non JSF / XPages environment -specifically speaking: scheduled agents  

Like I said, I have unfairly blamed the OpenNTF ODA for this -but the problem is actually an architectural one within the Domino server itself.  Scheduled agents run in their own little space, and the boundary between their space and the JSF / XPages environment is strong.  This means your awesome XPage code (and any Java code using the OpenNTF API) simply won't work in a scheduled agent.  

Again, this is not the fault of the ODA -it just gets hit hard with it because it is so very good at what it does.  We developers who use it have built all kinds of dependencies into our own APIs, and when we suddenly realize we can't use our APIs in a scheduled agent we tend to blame the ODA, instead of the design separation built into the server itself.

For now there really is no good solution to this conundrum.  

Please allow me to present an acceptable hack that will work for most instances.  

Scheduled XAgents


An XAgent is an XPage that does NOT render, but executes code during the beforePageLoad event.  This code runs within the JSF / XPages realm, which means it can touch your kickass ODA-dependent Java code.  

The way we schedule the XAgent is to create a scheduled agent that reaches out and hits your XAgent XPage.  We can pass information to the XAgent via QueryString parameters, or we could also (you need to do this yourself, I'm not writing that code today) pass the information via a back-end document on the server, similar to the way you can write information to a document from within the Notes Client and then call an agent on the server and have it read that information from the document.  

We can trigger our XAgent either via a NON-ENCRYPTED connection (which I do not recommend), or via an SSL-ENCRYPTED connection. Note that for Anonymous ACL to work on the database containing the XAgent XPage, it must be set to at minimum of READER. Also note that the scheduled agent code must be at minimum of security level 2 (Allow restricted operations), or it will not be able to connect to a URL.

UNSECURE connection code

import java.net.HttpURLConnection;
import java.net.URL;

import lotus.domino.AgentBase;

public class JavaAgent extends AgentBase {
// Change these settings below to your setup as required.
static final String targetURL = "http://URL_OF_YOUR_XPAGE_AND_OPTIONAL_QUERYSTRING";
static final String USER_AGENT = "Mozilla/5.0";


public void NotesMain() {
 try {
   final URL url = new URL(JavaAgent.targetURL);
   final HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();

   httpUrlConnection.setRequestMethod("GET");
   httpUrlConnection.setRequestProperty("User-Agent", JavaAgent.USER_AGENT);
   httpUrlConnection.getResponseCode();

 } catch (final Exception e) {
   // YOUR_EXCEPTION_HANDLING_CODE
 }
}
}



SSL-ENCRYPTED connection code

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

import javax.net.ssl.SSLSocketFactory;

import lotus.domino.AgentBase;

public class JavaAgent extends AgentBase {
// Change these settings below to your setup as required.
static final String hostName = "DNS_HOST_NAME_OF_YOUR_SERVER";
static final String urlFilepath = "FILEPATH_ON_HOST_TO_YOUR_XPAGE_AND_OPTIONAL_QUERYSTRING";
static final int sslPort = 443;


public void NotesMain() {
 try {
   final SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
   final Socket socket = factory.createSocket(JavaAgent.hostName, JavaAgent.sslPort);

   final BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
   final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

   final StringBuilder sb = new StringBuilder();
   sb.append("GET ");
   sb.append(JavaAgent.urlFilepath);
   sb.append(" HTTP/1.1\n");
   final String command = sb.toString();

   sb.setLength(0);
   sb.append("Host: ");
   sb.append(JavaAgent.hostName);
   sb.append("\n\n");
   final String hostinfo = sb.toString();

   out.write(command);
   out.write(hostinfo);
   out.flush();

   in.close();
   out.close();
   socket.close();

 } catch (final Exception e) {
   // YOUR_EXCEPTION_HANDLING_CODE
 }
}
}






Hope this helps!
Comments

1Don Mottolo  06/25/2015 4:29:41 PM  Scheduled XAgents

Nice! I had thought about calling an XAgent URL from a scheduled agent, but never got around to trying it. Glad to see that you have.

2Martin Pradny  06/26/2015 2:10:49 AM  Scheduled XAgents

For security concerns I usually set XAgent page for Public Access and also check that the connection is from localhost. This is enough for me to think that the solution is secure.

3Steve Pridemore  06/26/2015 9:41:04 AM  Scheduled XAgents

could you set the Authorization request header and get an authenticated connection?

4Olli Kämäräinen  09/24/2015 5:08:25 AM  Scheduled XAgents

Here is unfinished code to call XAgent with session based authentication. This example saves response as .csv file. It might not work on Client, but i succesfully ran it on server.

HttpURLConnection urlConn = null;

URL myURL = null;

String cookie = "";

String paramid = "TEST";

URL login = null;

try {

// HttpURLConnection.setFollowRedirects(false);

/*

* GET AUTHENTICATION COOKIE FROM SERVER

*/

login = new URL("{ Link }

String urlParameters = "username=User+Name&password=test123";

byte[] postData = urlParameters.getBytes( "UTF-8");

int postDataLength = postData.length;

urlConn = (HttpURLConnection) login.openConnection();

urlConn.setDoOutput(true);

urlConn.setRequestMethod("POST");

urlConn.setRequestProperty( "Content-Type","application/x-www-form-urlencoded");

urlConn.setRequestProperty( "charset","utf-8");

urlConn.setRequestProperty("Accept-language","en");

urlConn.setRequestProperty("User-Agent","User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");

urlConn.setRequestProperty( "Content-Length", Integer.toString( postDataLength ));

DataOutputStream wr = new DataOutputStream( urlConn.getOutputStream());

wr.write( postData );

urlConn.connect();

wr.close();

cookie = urlConn.getHeaderField("Set-Cookie");

if (cookie!=null ) {

/*

* COOKIE FOUND -> CALL XPAGE

*/

myURL = new URL("{ Link }

urlConn = (HttpURLConnection) myURL.openConnection();

urlConn.setDoOutput(true);

urlConn.setRequestMethod("GET");

urlConn.setRequestProperty("Cookie", cookie.substring(0, cookie.indexOf(";"))); //add cookie to request

urlConn.setRequestProperty( "Content-Type","application/x-www-form-urlencoded");

urlConn.setRequestProperty("Accept-language","en");

urlConn.setRequestProperty("User-Agent","User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");

urlConn.connect();

BufferedReader br = new BufferedReader(new InputStreamReader((urlConn.getInputStream())));

StringBuilder sb = new StringBuilder();

String output;

int row = 0;

while ((output = br.readLine()) != null) {

if (row>=1) {

sb.append("\n");

}

sb.append(output);

row++;

}

Database db = session.getCurrentDatabase();

Document attdoc = db.createDocument();

attdoc.replaceItemValue("Form", "Attachment");

MIMEEntity m2;

MIMEEntity tmp1;

Stream stream;

m2 = attdoc.createMIMEEntity("Body");

tmp1 = m2.createChildEntity( );

stream=session.createStream();

stream.write( sb.toString().getBytes() );

MIMEHeader hdr = tmp1.createHeader("Content-Disposition");

hdr.setHeaderValAndParams("attachment; filename=\"order.csv\"");

tmp1.setContentFromBytes( stream, "application/csv", MIMEEntity.ENC_IDENTITY_BINARY);

attdoc.closeMIMEEntities(true, "Body");

attdoc.save(true, true);

System.out.println("Attachment saved");

br.close();

}

} catch(Exception e) {

System.out.println("Error " + e);

e.printStackTrace();

}

5Devin Olson  09/28/2015 5:56:18 PM  Scheduled XAgents

@Steve: I'm not certain, I have not tested that.

6Howard  04/06/2016 5:46:48 PM  Scheduled XAgents

Fix Pack 5 might have some issues here with the Java agent connecting via SSL to the server. It appears some of the older ciphers that Java used were disabled in Domino in FP5...

7Howard  04/07/2016 7:04:35 PM  Scheduled XAgents

The issue with SSL and FP 5 is that IBM changed the the minimum "well known" DH key size allowed to 2048 bits. Java 6 in Domino only supports 1024. You have to create a custom DHE key group that has a 1024 key. See https://www-10.lotus.com/ldd/dominowiki.nsf/dx/TLS_Cipher_Configuration for more information.

Howard