Using easymock for disconnected Domino Java testing

One of the projects I'm working on at the moment has a *lot* of code written in a J2EE application which talks to Oracle using JDBC and Domino over CORBA. It actually all works really well but the approach of one of our non-Domino developers was really interesting to me, so I asked if I could write about it up here.

So the approach that Greg takes is test driven development, that is he writes his tests before writing the code. For his testing he would rather not connect to the Domino server all the time, but instead mock up objects to make sure that his other code works correctly. Enter EasyMock, which is a JAR file which you can include in any project that basically pretends to be whatever object you want it to be.

package test;

 

import junit.framework.*;

import java.util.Vector;

import org.easymock.EasyMock;

import org.easymock.*;

import static org.easymock.EasyMock.*;

 

import lotus.domino.*;

import com.agcs.cores.utilities.*;

 

 

public class testDominoFieldProcessing extends TestCase {

 

  Document mock = null;

  DateTime datemock = null;

  DominoFieldProcessing dfield = null;

  public void setUp() throws Exception

  {

 

    mock = createMock(Document.class);

 

    Vector<DateTime> v = new Vector<DateTime>();

    for (int i = 0; i< 3;i++)

    {

      datemock = createMock(DateTime.class);

      expect(datemock.getLocalTime()).andReturn("01:02:03");

      expect(datemock.toJavaDate()).andReturn(new java.util.Date(System.currentTimeMillis()   ));

      v.add(datemock);

      replay(datemock);

    }

 

    expect(mock.getItemValue("time")).andReturn(v);

    expect(mock.hasItem("time")).andReturn(true);

    replay(mock);

 

  }

 

 

  public void testNotesDateToJavaDate()  throws Exception

  {

    for (java.util.Date d:  DominoFieldProcessing.getFieldValueAsListDate(mock,"time"))

      System.out.println(d);

  }

}
Java2html
What this code shows is the use of EasyMock to be a Notes Document and a DateTime Object. You tell it what the methods that you want to call should return, and then, for all intents and purposes, the object is a Domino object but without having to go through the hassle of deploying your code to a server which has the rights to connect to a proper Domino server. Now of course this should only be used early on in the development lifecycle, but for environments where connecting to Domino can be difficult this is a real time saver.

WebDAV tip

I spent a little time last night getting WebDAV enabled on a database and server so that I could upload the latest version of Dojo as file resource design elements. Unfortunately it took a little more time than expected. Of course Jake has an article on how to set it up but the one thing which I missed for about half an hour of swearing was that you must check the design locking box in the database properties as well as everything else. The Administration documentation is ambiguous on this point from my view.

So hopefully if people are seeing the message "The HTTP method is not allowed for the specified URL" when trying to copy a file into the Domino database using Windows Explorer (or indeed the OS X Finder although the error message is somewhat less helpful there!) then a quick Google should find this page.

LotusScript to Make a new web user able to log in immediately

Bill offered some advice yesterday about a problem I've been having with new user registrations for Defectr.

What was happening was that a new user would register, everything would work fine but it took up to 15 minutes for the new account to work. I thought I had tried all combinations of solution but am also running the Defectr registrations in a Directory Assistance NAB (ie not the primary NAB on the server) so assumed that that was the source of the problem. But Bill's posting spurred me to have another quick look and it seems like I've got it fixed now.

All of the work I had been doing was around refreshing views in the NAB in which the users were being created, it never occurred to me to refresh the same views in the main NAB but when I did the new account worked straight away. I have no idea why this would be the case, any idea you admin types?

Anyway, this is the code I now run when a new user registers:
Option Public Option Declare Use "OpenLogFunctions" Use "slnnotesdll" Sub UpdateNAB On Error Goto Whoops Dim commander As New nnotesdll Dim sess As New NotesSession Dim strError As String Dim server As String Dim scommand As String Dim strResponse As String Server = sess.CurrentDatabase.Server sCommand = |load updall names.nsf -t "($ServerAccess)" -r| strResponse="" strError=commander.rConsole(Server,sCommand,strResponse) Sleep 1 sCommand = |load updall names.nsf -t "($Users)" -r| strResponse="" strError=commander.rConsole(Server,sCommand,strResponse) Sleep 1 sCommand = |dbcache flush| strResponse="" strError=commander.rConsole(Server,sCommand,strResponse) Sleep 1 sCommand = "Tell Adminp Process People" strResponse="" strError=commander.rConsole(Server,sCommand,strResponse) Sleep 1 sCommand = "Show nlcache reset" strResponse="" strError=commander.rConsole(Server,sCommand,strResponse) Exit Sub Whoops: Call LogError() gResponse = "ERROR: " + Error Exit Sub End Sub
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

Tags: Show-N-Tell Thursday

LotusScript Class to Convert Numbers to Words in French

A bit of an obscure one this, but we are making an English application multi lingual. One function converts numbers to words, and having tried to use the same logic for the French translations it just doesn't work so I spent a couple of hours this morning porting a Java class to work in LotusScript to perform the function:

'**********************************************************
'*Ported from Java Class found at:
'*http://www.rgagnon.com/javadetails/java-0426.html
'**********************************************************
Class FrenchDecimalFormat
  tenNames() As String
  uniteNames1() As String
  uniteNames2() As String
  
  Sub new()
    Redim tenNames(9)
    tenNames(0) = ""
    tenNames(1) = ""
    tenNames(2) = "vingt"
    tenNames(3) = "trente"
    tenNames(4) = "quarante"
    tenNames(5) = "cinquante"
    tenNames(6) = "soixante"
    tenNames(7) = "soixante"
    tenNames(8) = "quatre-vingt"
    tenNames(9) = "quatre-vingt"
    
    Redim uniteNames1(19)
    uniteNames1(0) = ""
    uniteNames1(1) = "un"
    uniteNames1(2) = "deux"
    uniteNames1(3) = "trois"
    uniteNames1(4) = "quatre"
    uniteNames1(5) = "cinq"
    uniteNames1(6) = "six"
    uniteNames1(7) = "sept"
    uniteNames1(8) = "huit"
    uniteNames1(9) = "neuf"
    uniteNames1(10) = "dix"
    uniteNames1(11) = "onze"
    uniteNames1(12) = "douze"
    uniteNames1(13) = "treize"
    uniteNames1(14) = "quatorze"
    uniteNames1(15) = "quinze"
    uniteNames1(16) = "seize"
    uniteNames1(17) = "dix-sept"
    uniteNames1(18) = "dix-huit"
    uniteNames1(19) = "dix-neuf"
    
    Redim uniteNames2(10)
    uniteNames2(0) = ""
    uniteNames2(1) = ""
    uniteNames2(2) = "deux"
    uniteNames2(3) = "trois"
    uniteNames2(4) = "quatre"
    uniteNames2(5) = "cinq"
    uniteNames2(6) = "six"
    uniteNames2(7) = "sept"
    uniteNames2(8) = "huit"
    uniteNames2(9) = "neuf"
    uniteNames2(10) = "dix"
  End Sub
  
  Function convertZeroToHundred(number As Integer) As String
    Dim laten As Integer
    Dim lUnite As Integer
    Dim result As String
    Dim joiner As String
    laten = Fix(number / 10)
    lUnite = number Mod 10
    result = ""
    
    If laten = 1 Or laten = 7 Or laten = 9 Then
      lUnite = lUnite + 10
    End If
    
    joiner = ""
    If laten > 1 Then
      joiner = "-"
    End If
    If lUnite = 0 Then
      joiner = ""
    Elseif lUnite = 1 Then
      If laten = 8 Then
        joiner = "-"
      Else
        joiner = " et "
      End If
    Elseif lUnite = 11 Then
      If laten = 7 Then
        joiner = " et "
      End If
    End If
    If laten = 0 Then
      result = uniteNames1(lUnite)
    Elseif laten = 8 Then
      If lUnite = 0 Then
        result = tenNames(laten)
      Else
        result = tenNames(laten) + joiner + uniteNames1(lUnite)
      End If
    Else
      result = tenNames(laten) + joiner + uniteNames1(lUnite)
    End If
    
    
    convertZeroToHundred = result
  End Function
  
  Function convertLessThanOneThousand(number As Integer)
    Dim hundreds As Integer
    Dim remainder As Integer
    Dim strRemainder As String
    Dim result As String
    hundreds = Fix(number / 100)
    remainder = number Mod 100
    strRemainder = convertZeroToHundred(remainder)
    
    If hundreds = 0 Then
      result = strRemainder
    Elseif hundreds = 1 Then
      If remainder > 0 Then
        result = "cent " + strRemainder
      Else
        result = "cent"
      End If
    Else
      If remainder > 0 Then
        result = uniteNames2(hundreds) + " cent " + strRemainder
      Else
        result = uniteNames2(hundreds) + " cents"
      End If
    End If
    convertLessThanOneThousand = result
  End Function
  
  Function convert(number As Double)
    Dim snumber As String
    Dim billions As Integer
    Dim millions As Integer
    Dim hundredthousands As Integer
    Dim thousands As Integer
    Dim strBillions As String
    Dim strMillions As String
    Dim strHundredThousands As String
    Dim strThousands As String
    Dim result As String
    If number = 0 Then
      convert = "zéro"
      Exit Function
    End If
    
    snumber = Cstr(number)
    snumber = Format$(number, "000000000000")
    billions = Cint(Left$(snumber, 3)) ' XXXnnnnnnnnn
    millions  = Cint(Mid$(snumber, 4,3)) ' nnnXXXnnnnnn
    hundredthousands = Cint(Mid$(snumber, 7, 3))  ' nnnnnnXXXnnn
    thousands = Cint(Mid$(snumber, 10, 3))  ' nnnnnnnnnXXX
    
    If billions = 0 Then
      strBillions = ""
    Elseif billions = 1 Then
      strBillions = convertLessThanOneThousand(billions) + " milliard "
    Else
      strBillions = convertLessThanOneThousand(billions) + " milliards "
    End If
    result =  strBillions
    
    If millions = 0 Then
      strMillions = ""
    Elseif millions = 1 Then
      strMillions = convertLessThanOneThousand(millions) + " million "
    Else
      strMillions = convertLessThanOneThousand(millions) + " millions "
    End If
    result = result + strMillions
    
    If hundredthousands = 0 Then
      strHundredThousands = ""
    Elseif hundredthousands = 1 Then
      strHundredThousands = "mille "
    Else
      strHundredThousands = convertLessThanOneThousand(hundredthousands) + " mille "
    End If
    result = result + strHundredThousands
    
    strThousands = convertLessThanOneThousand(thousands)
    result =  result + strThousands
    
    convert = result
  End Function
End Class&#0;&#0;&#0;

Tags: Show-N-Tell Thursday

Posting to a Form for AJAX validation

I am guessing that this has been done before elsewhere but it was a new idea for me anyway.

A couple of weeks ago Julian and Philippe were talking about using the Scriptaculous Autocomplete feature. Philippe had the cracking idea of using a page to do DBLookups on the fly to provide JSON data.

While on the train this morning I came up with the idea of performing complex form validation using a similar technique. The sort of thing I am thinking of is uniqueness checking, complex business rules and so on but without the overhead of firing up an agent to do the processing, submitting to a form will do that same task much faster.

It's definitely worth having a read of Philippe's article that I mentioned earlier. What I am proposing is that rather than running a "GET" request from a page which he suggested for the autocomplete tool, that we create a simple form which we "POST" to, to make sure that all of the fields are valid, before submitting the "real" form to be saved. Let me describe things in a little more detail. I have a form which is registering a new user for an application. I need to make sure that the company name of the user is valid and that his email address has not been used for any other users that are already registered. Now this is pretty simple stuff to handle with normal form validation or webquerysave agents but I am not a big fan of the user submitting, waiting for a screen refresh only then to be told they've got something wrong.

So instead, before submitting the form, we run some javascript which creates an AJAX request (as usual I am using Prototype but you can use your preferred framework). My submit function looks something like this:
function submitClient() {
//Now we do the server side validation
var a=$H(
{
clientnameandcode: $F("clientnameandcode"),
email: $F("email")
}
);
var myAjax = new Ajax.Request(
'/validateNewclient?createdocument&rand=' + Math.floor(Math.random()*1001),
{method: 'post', parameters: a.toQueryString(), onComplete: submitclientContinue}
);
}
function submitclientContinue(jsonDoc) {
if (jsonDoc.responseText == "\n") {
//All the validation has been passed so submit properly
document.forms[0].submit();
}else{
//There are error messages so display them in a div on the form
$("validation").innerHTML = "

" + jsonDoc.responseText +"

"; $("validation").style.display = ""; } }

The two fields I am interested in, clientnameandcode and email are posted to the form called "validateNewClient". The form then has the following design:



The things to note are...
  • The SaveOptions field which is set to "0" to prevent the document being saved
  • The two data fields which match the parameters sent in by the AJAX request
  • The $$Return formula which actually does all of the validation work, it just computes some response messages where there are errors, or empty string for successes.

    Now the only "hack' I've had to do is with the return value from the $$Return, where because @SetHTTPHeader("Content-Type"; "text/text") doesn't work I have to redirect the message I want to return to a page where I can format the message as simple text to be returned to the AJAX request.

    This isn't an approach which I would recommend for every situation, but where you have relatively complex business rules which need to be validated and the network can handle the extra load etc then I think it's quite neat.

    Of course the one caveat is that with client side validation you must always, always double check on the server side to prevent sneaky people circumventing all of the Javascript you have written to make their lives easier!

    Technorati Tags: Show-N-Tell Thursday