Notes on Mobile
Getting the URL parameters in a Domino Java web agent

In my dataProxy agent I needed an easy way to get all the URL parameters. I figured I would post that in a separate entry so it wouldn’t get lost.

	private HashMap getURLParameters() throws NotesException {
        String url = agentContext.getDocumentContext().getItemValueString("QUERY_STRING");

        String[] vals = url.split("&");
        HashMap m = new HashMap();
        for( int i = 0; i < vals.length; i++){
        	String[] p = vals[i].split("=");
        	if( p.length == 2 ){
	        	try {
					m.put(p[0], URLDecoder.decode(p[1],"UTF-8") );
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        	}        	
        }
		return m;
	}

I can then save them in a member of my agent as a HashMap like param.get(“db”) or param.containsKey(“db”) and can get at them where ever I am.

Thanks for nothing…

Well that was a total waste of a morning. Long story short was that I wanted to be able to create documents in a Domino database, but not require a form to do it. Naturally I created an agent that my HTML form could post to. All was going well, but I decided that I also wanted to be able to attach an image as well as some simple form fields. So, after a little more work, my agent reading the ‘multipart/form-data’ with the help of the Apache commons fileupload library. That itself was a bit of a trick since all the examples assume you are using it in a servlet context. There is a way however, to use the agent’s DocumentContext document instead of the HTTPRequest object. All this was working fantastic… only when I started looking at the attachments I created did I discover a huge(!!!) problem. My images were corrupt. At first I though maybe I didn’t have something set right with the Apache code, but alas, after wasting an entire morning, it turns out Domino was to blame. On top of that, it apparently has been a problem for a long time and is still, broken on 8.5.2.

What is happening is that when Domino writes the binary data coming from the HTTP request to the DocumenContext document, it converts the contents to LMBCS and thus totally messes up my binary POST data. There is nothing I can do to get access to the original data, short of writing a actual servlet - which probably won’t work anyway.

ARRGGG!!!!!

Fine. Lesson learned. Domino is not a good HTTP server. Ya I know there are easier ways to do this with forms and file upload controls and what not but that’s not the point though. This stuff should work.

Notes documents as JSON

There are a couple of ways to do this online, but having a Java agent to do view JSON gave me a good opportunity to do it my own way. I have a couple things going for me at the moment - I don’t need rich text (yet), and I don’t need to be able to create or update documents (yet).

The ‘Document’ class in Java has a function called generateXML which has been around for a long time (5.0.3 according to the doc) so it should be relatively stable, but as with all things Domino… we shall see if it is in practice. That got me the DXL version of a Notes document. To get the JSON I went to github and got the org.json classes and imported them into a Java script library in Domino:

This has a lot of really handy classes in it, but what I really wanted was the XML class that has a toJSONObject method. With all these pieces, it was really easy to put the following method together:

    private void writeDocument(Database db, String documentId, boolean asJSON) throws NotesException, IOException, JSONException {
    	Document doc = db.getDocumentByID(documentId);
    	if( null== doc){
    		doc = db.getDocumentByUNID(documentId);
    	}
     	if( null== doc){
     		System.out.println("document not found");
     		return;
     	}
     	
		// write the document out
		java.io.Writer sw = new java.io.StringWriter();
     	doc.generateXML(sw);
     	sw.flush();     
		PrintWriter ao = getAgentOutput();
		if( asJSON ){
			ao.println("Content-type: application/json");
			ao.print(XML.toJSONObject(sw.toString()).toString());
		}
		else{
			ao.println("Content-type: text/xml");
			ao.print(sw.toString());
		}
     	ao.flush();
    }

I threw that into the dataProxy agent and added 2 URL parameters:

  • documentId= Lets me specify the document I want to access
  • outputformat=JSON Omitting this parameter keeps the output in DXL.

That’s it! With the next post I’ll show you how to integrate this into the mobile app.

Working around Domino

Seems like I do this a lot… so after running into a bug in the Domino JSON view output I was faced with a few choices. The first, and probably easier option, was to just rewrite the views I was using to fix the encoding problem I ran into. I ended up not using that option because it would mean that I would have to have custom views for all the data I was accessing. Even though I had created the views new for this exercise, ultimately I wanted to transition to preexisting views in the apps once I was ready to move to production. So now that I was faced with having to make design changes to the back end apps anyway, I decided to explore my other option which was to create a custom agent to serve up the data.

A custom agent has a couple disadvantages. First, it will require some special considerations when it comes time to deploy this regarding ACLs and agent signers. Second, it means that I have to re-implement something that already exists in Domino. The first one is just a fact of life and can be dealt with. The second one is more problematic because, I hate doing it, and I won’t be able to implement all the possible options and it will require a lot more testing.

On the up side, a custom agent does have many advantages also. First, I can make the output anything I want, or include as much or as little of the data. Second, I now have a chance to standardize an API that I can easily port/re-implement on other back-end systems without impacting my front end (big plus at the moment). Third, the ReadViewEntries does not support JSONP, which might come in handy at some point in this project I’m sure. And Finally, it can give me access to data a view cannot, like rich text. With these in mind the choice to custom code the agent became much easier to accept.

The dataProxy Agent

I wrote the agent in Java because LotusScript is just no fun anymore, and it gives me the most flexibility when I comes time to port this to some other platform. The basics of the agent was to try and get as close to the ReadViewEntries format as possible so I took the output from that and loaded it up in a text compare program so I could see it side-by-side with my output. Form there I just tweaked my code until I had the same, or nearly the same output. In about an hour I had the following code:

	private void writeViewJSON(Database db, String viewName) throws NotesException {
		// get the view
		View v = db.getView(viewName);
		v.setAutoUpdate(false);
		// get the navigator
		ViewNavigator nav = null;
		if (params.containsKey("RestrictToCategory")) {
			nav = v.createViewNavFromCategory((String) params.get("RestrictToCategory"));
		} else {
			nav = v.createViewNav();
		}

		// write the view out
		PrintWriter ao = getAgentOutput();
		ao.println("Content-type: application/json");
		ao.println("{");
		ao.println("\"@timestamp\": \"" + v.getLastModified() + "\",");
		ao.println("\"@toplevelentries\": \"" + v.getTopLevelEntryCount()
				+ "\",");
		ao.println("\"viewentry\": [");
		ViewEntry e = nav.getFirst();
		for (int i = 0; i < nav.getCount(); i++) {
			ao.println("{");
			ao.println("\"@position\": \"" + e.getPosition('.') + "\",");
			ao.println("\"@unid\": \"" + e.getUniversalID() + "\",");
			ao.println("\"@noteid\": \"" + e.getNoteID() + "\",");
			ao.println("\"@siblings\": \"" + e.getSiblingCount() + "\",");
			ao.println("\"entrydata\": [");
			int writeCount = 0;
			for (int j = e.getIndentLevel(); j < e.getColumnValues().size(); j++) {
				ViewColumn c = (ViewColumn) v.getColumns().elementAt(j);
				if (!c.isHidden()) {
					if (writeCount++ > 0) {
						ao.println(",");
					}
					Vector cv = writeColumn(ao, e, c);

					if (c.isCategory()) {
						break;
					}
				}
			}
			ao.println("]");

			ao.println("}");
			if (i + 1 < nav.getCount()) {
				ao.println(",");
			}
			ao.println();
			e = nav.getNext(e);
		}
		ao.println("]");
		ao.println("}");
	}

Some of the differences, though minor, are to do with categories and the NoteId and UNID. In the Domino output you get special NoteId’s and in my version they are just empty strings. Also, the timestamp that I output is a different format than the domino version.

The agent supports a few URL parameters:

  • s= Lets you specify the server.
  • db=   Lets you specify the database the view is defined in.
  • v= Lets you specify the view to read.

using these, I can get to any view in any database.

That was all I needed at this point on the server. On the client side, it only required a single change to the Javascript to change the URL in the JSON calls. 

Not the end of the story…

This isn’t the end of the agent though. I have also added another function that makes a huge difference in the client that I didn’t have the ability to do before. I will cover that in my next post.