Blogs

  • Browse Blogs
  • My Blog
  • My Updates

Tags Help

  • View as cloud  | list

Similar Blogs

photo

Uh Clem's Adm...

35 Entries |  Chris Mobley
Updated 
Ratings 5     Comments 42
photo

J. Keil Wilso...

9 Entries |  Keil Wilson
Updated 
Ratings 4     Comments 7
photo

Yellow is the...

55 Entries |  Tim Tripcony
Updated 
No Ratings 0     Comments 22
photo

.Domino Frame...

86 Entries |  Peter Presnell
Updated 
Ratings 2     Comments 80
photo

Lotus Nut

69 Entries |  Chris Whisonant
Updated 
Ratings 4     Comments 96

Jan Schulz

Blog Authors:  Jan Schulz  

Compile Problem fixed: Scripname was too long

Jan Schulz  |     |  Tags:  public lotusscript name duplicate compiling  |  Comments (0)
Just a short follow up to my problem from this afternoon about recompile going into an endless loop or not saving at all, when changing the order of the Use statements (Error "Duplicate PUBLIC Name";): it seems that the compiler can't cope with long names of script libs. My scriptlib was named "de.katzien.ls.MVC.Model.MonitoringCapability". Changing it to "de.katzien.ls.MVC.Model.Monitoring" fixed the problem.

Sending Designer into endless loops with "Recompil...

Jan Schulz  |     |  Tags:  lotusscript designer rant  |  Comments (1)
... or to hell...

Currently I'm trying to build an LS framework, which is runtime configurable. Instead of using static functions or simple class extention, I try to build an object graph, where each object adds some functionality. So something which needs to run after a Document is saved will add a observer to the document model and do the work in the right callback method. This results in small objects and class libs, but lots of use of "Use". Unfortunatelly designer seems to be unable to cope with this. Right now I'm in a situation, where designer compiles my code, if I press strg+s, but is sent into an endless loop if I use "Recompile all Lotusscript".

Other fun includes "Use" ordering:
this compiles with strg+s (but sends "Recompile all" into an endless loop):
Use "class::logger"
Use "de.katzien.App.Infomail"
Use "de.katzien.App.InfomailDB"
Use "de.katzien.ls.Email"
this not:
Use "class::logger"
Use "de.katzien.App.InfomailDB"
Use "de.katzien.App.Infomail"
Use "de.katzien.ls.Email"
The only change is the ordering... Error: Duplicate PUBLIC Name "CHANGE_LISTENER_EVENT_BEFORE_CHANGING" in USE de.katzien.App.Infomail
This name is the public Const in script lib "MonitoringCapability", which is use'd directly by .App.Infomail and indirectly by .App.InfomailDB (uses InfomailEntry which uses MonitoringCapability). setting this to private just ends up with the next error and I can't set a complete class to private if I want to use that class in some other lib...

And don't even ask about "Illegal Circular Use".

@IBM: Please improve the Compiler for LotusScript!

Fun with ... garbage collection

Jan Schulz  |     |  Tags:  oop  |  Comments (0)
Todays fun: my logger (basicly an AgentLog, but with an underlying NotesDocument) opened a second doc where it shouldn't and worse didn't save the first document with the proper logging output. Turned out, that the last logAction call in the logger.delete sub (called by the garbage collector) found that the underlying logdoc was nothing (and so not saved until I turned on flushAlways) and so created a new one with only the "new log opend" and "log closed" message in it. Fun if you expect half a page of debugging output for an webagent (which you need because the agent is not working :-/ ) and only get "Log opened and closed"...

The fix was to cache the parent Database of the Logdocument in the Logger object.

This Database was actually cached (Dim logDB...) in the OpenLogFunction Declaration section (the Logger is an extention), but it seems that this script lib was garbage collected before calling delete on my object and as the DB got collected, so gets every object which came from that DB. By-by logdoc...

It seems that something changed between last years domino (7.0.x or 8.0.1, not sure which I tested on) and 8.0.2 in the garbage collection routine, as the same logger setup worked some month ago without this problems. Or I garabage collection is influenced how many classes/ScriptLibs you use in the your agent...

So, todays lesson learned: Do not cache objects in other scriptlibs Declaration section if you use that object in delete... Fortunately this is my only class with a deconstructor :-)

How to test if an array is initialized?

Jan Schulz  |     |  Tags:  lotusscript array  |  Comments (1)
I like to write code like this:


public function getSomething() as Something
if oSomething is nothing then
set oSomething = new Something()
end if
set getsomething = oSomething
end function

How do this with dynamic arrays? I was very surprised when this threw an error:


dim arrSomethings() as Something
if isArray(arrSomethings) then print ubound(arrSomething) ' unitialized array error when calling the ubound function

You have to call a redim arrSomething(0 to 0) bevor you can do anything with that array. I tested it with every isXYZ function I could find in the designer help, but i couldn't find a way to detect that...Somehow I agree with Tommy Valand: LS Arrays are strange. Give me a Java collections like API and I'm happy :-)

BTW: datatype (arrSomething) gives "8738", even after the redim, but the designer help says that "8704" is a Dynamic array. Bug?

Help: Messages from the future: "Invalid Time or D...

Jan Schulz  |     |  Comments (1)
Hello LazyWeb,

we had a user who accidentally changed his system date to sometime in the future (year 3434...) and did some work in his local mailfile and replicated... <insert fun with local replication which resulted in delting all his local replics here>. Since then we get this nice message on the servers:
03.10.2008 03:25:07   Error reading RevisionTime for folder operations in database <mailfile.nsf>. Run fixup on the database.: Invalid Time or Date Encountered

Needles to say, that fixup does not make this message go away :-( Anyway: all docs with "funny" dates are gone from the mailfile, there seems to be no folder with such funny dates and I purged the deletion stubs, but the message still comes _twice_ on every fixup.

The last time we just "sat it out", as it was just one month in the future, but waiting to year 3434 is probably not an option.

Anyone with an Idea?

File - Application - Install

Jan Schulz  |     |  Tags:  lotus install notes 8.0.2  |  Comments (0)
This is mainly here so that I don't have to google another half an hour...

The Policy setting to get the install menu back is in "Desktop policy settings -> Basics -> Provisioning -> Allow User initiated updates"

Additional buzzwords following:
com.ibm.notes.branding/enable.update.ui=true, plugin_customization.ini, 8.0.2, install, update, updatesite

Thanks to Henning Kunz, where I finaly found this.

Notes becomes stranger every day: binding to onHel...

Jan Schulz  |     |  Tags:  event lotusscript binding  |  Comments (3)
Binding to an onHelp event via "On Event Onhelp From fUIDoc Call delegate_onHelp" is only working, if you have any code/Comment in the onHelp event on the Form: just leaving it with no comment in it in the client lotuscript part will not trigger delegate_onHelp. Putting a comment into the event on the form works:
image
I found that only because I wanted to test if this code element is triggered at all and then commented out my code. And behold: it worked from there...

Can someone explain this?

I think I saw a different case, where putting a comment into some otherwise empty code block made a difference, but I can't remember anymore. IMO this is just weird...

Now I only need the LS version of OpenHelpDocument :-)

NotesUIWorkspace.openHelpDocument(...)?

Jan Schulz  |     |  Comments (0)
I implemented a context sensitive help in one of my applications, but now I want to migrate this code to an event based handler which uses the 'onHelp' NotesUIDocument event. Unfortunatelly I can't find a replacement for this code in LotusScript: @Command([OpenHelpDocument]; ""; "LHelp";"FORM-"+ Form))

Is there no other possibility than using evaluate?

Extending OpenLog

Jan Schulz  |     |  Comments (0)
I use OpenLog for my error handling and mostly it just works. But there are a few cases, when I wanted to have something more:
  • show the error messages to the user and get some information what the user did
  • Sending the logentry via mail to the central OpenLog DB in case the user is offline
While I was at it, I also added "Handlers": you can pass in a handler and openlog will ask each handler if the error can be handled (ignored, resolved, etc) by the handler and if so, will return. I got the Idea for error handlers from the "Lotusphere 2008 - AD311 - Object Oriented Programming with Lotusscript – Take It To the Next Level" presentation/ example DB.

Now you can write something like this:
handleerror:
If handleError() Then Resume Next
There is also a new "handleOrRethrowError() method, which corresponds to "addToStackTrace()" but can handle an error.

This handler mechanism are a great way to let someone else extend your code without changing your code. Everything what is needed are some entry points, where you can add you own "HandlerObjects" and of course your code must delegate the handling to it.

This is the new handleError() method I came up with:
Public Function handleError() As Boolean
'** logs whatever error happens to be on the stack (if any)
On Error Goto processError

Dim session As New NotesSession

handleError = False

' First try to handle the error
' Loop over some handler List
' If any can and has handled this error, just return true

Forall handler In errorHandlers
If handler.isResponsibleFor(Err) Then
handleError = handler.handleError()
End If
If handleError = True Then
Exit Function
End If
End Forall

'if not:
' Create a log item and a document
' Ask the user for input with a dialog

Call setupLogItem("","",Nothing,Getthreadinfo(10)) 'refactored to get rid of code duplication
Dim db As NotesDatabase

If session.IsOnServer Then
' I can garantee that there is a OpenLog DB on each server
Set db = GetLogDatabase
globalLogSuccess = globalLogItem.WriteToLog(db)
Else
' local means in most cases not online -> mail it and ask for input
Set db = New NotesDatabase("", "")
Call db.OpenMail
Dim dummyDoc As NotesDocument
Set dummyDoc = globalLogItem.CreateLogDoc(db)
' Just shows a dialog, which displays some fields from the LogItem
If askForAdditionalInformation(dummydoc) Then
globalLogItem.message = globalLogItem.message + Chr(13)+Chr(10)+ dummydoc.GetItemValue("ErrorMessage")(0)
End If
Call dummydoc.Remove(True)
' creates a doc and uses doc.send(...) to mail it to the OpenLog Mail-In DB
globalLogSuccess = globalLogItem.sendViaMail(db)
End If

Call ClearStackTrace()
If Not globalLogSuccess Then
Error 1001, "Log was not saved or sent"
End If

Exit Function
processError:
DebugPrint(StandardErrorMessage(Err, Erl, Error$, Getthreadinfo(1)))
Resume Next

End Function


I'm not yet satisfied: there is still some possibility to refactore code (there is much code duplication in the static methods) and I think most of the setup code (setupLogItem(...) and also for the stackTrace) could go into the classes. Also the dialog could go into a ErrorHandler: just let isResponsible(...) return true if not session.isOnServer and then return false from the handleError(). But this would need some priority in the handlers (no need to ask the user if we can handle it internaly) and a way to indicate if we are in the rethrow phase or in the final handleError()... Let's see....


Another thing is adding Context to the error (also an idea I got from the above example DB...). LogErrorEx(...) already lets you pass in some information, but it would be great if this could be more than a string: something like an object store, some variable values,... No idea yet how to implment that without registering every variable/object in a store :-/ Any Ideas?


assertThat(me, is_(aBlogPost()).and_(is_(interesti...

Jan Schulz  |     |  Tags:  testing validation hamcrest  |  Comments (1)
A few days ago I found Hamcrest on the web. It's a framework, which lets you write validation rules in almost plain english:
Dim value as Variant
value = "Hallo"
Call assertThat(value, is_( not_( equalTo("Hallo") ) ).and_(is_(ofType("String")))

Resulting in output like this:

Assertion failure: expected: is not equal to Hallo and is of type STRING; got: (STRING) Hallo

(bold is from the Matcher (second argument to assertThat), rest from the Error, which the assertThat method throws)

The Implementation is quite easy: you have a IMatcher abstract Class with several concrete matcher implementation and a static method, which returns a object of that matcher. The IsMatcher (just to have proper english sentences :-) and the static method looks like this:

Public Function is_(matcher As IMatcher) As IMatcher
Set is_ = New IsMatcher(matcher)
End Function

Class IsMatcher As IMatcher
Private fMatcher As IMatcher

Sub new(matcher As IMatcher)
Set fMatcher = matcher
End Sub

Public Function matchesSafely( value As Variant) As Boolean
matchesSafely = fMatcher.matches(value)
End Function

Public Function describeMeTo(description As Description)
description.appendText("is")
Call fMatcher.describeTo(description)
End Function
End Class

The testThat (just returns a boolean, assertThat uses that to throw the error) is just:

Public Function testThat(value As Variant, matcher As IMatcher) As Boolean
testThat = matcher.matches(value)
End Functio

IMatcher.matches(variant) is a wrapper to get the IMatcher.and_(IMatcher) and IMatcher.Or_(IMatcher) methods working. In the end it returns the logical result of all included Matchers.

The IMatcher.decribeMeTo(Description) is again the internal method (describeTo(...) is the wrapper to get AND and OR working) used to get the plain english description from the matcher composite (the bold Text above).

Dim oDescription As New Description
Call matcher.describeTo(oDescription)
print oDescription.toString()


Implemented Matchers up to now: is, not, equalTo, ofType, instanceOf, like. Feel free to add some matchers or leave a comment for other ideas :-)

Here is the script lib as it is up to now (BleedYellow seems to not allow any Notes/Domino related content: no *.lss, no *.nsf):

Edited, as I can upload now: LS-Hamcrest.lss

Save as Draft not working?

Jan Schulz  |     |  Comments (1)
I get an error notice when I try to save a blogpost as draft, loosing all my data (back does not work in Firefox :-( ). Anybody else with that?

Ok, the blog post about
Call assertThat("Hallo", is_( not_( equalTo("Hallo") ) ).and_(is_(ofType("String"))))

Assertion failure: expected: is not equal to Hallo and is of type STRING; got: (STRING) Hallo
 will have to wait until tomorrow :-)

testable code = better code?

Jan Schulz  |     |  Tags:  unittest design pattern oop  |  Comments (0)
After my entry about event delegation, I went out and refreshed my memory about Design Pattern (The GOF book was the first - and up to now my only - book about programming. Hey, It's just a hobby for me :-). I also found the Google Testing Blog. Especially one of the entries was very interesting: writing testable code. Here are my Top7 from that entry:
  1. Don't mix object graph construction with application logic
  2. Ask for things, Don't look for things (aka Dependency Injection / Law of Demeter)
  3. Don't do work in constructor
  4. Favor composition over inheritance
  5. Favor polymorphism over conditionals
  6. Don't mix Service Objects with Value Objects
  7. Mixing of Concerns
So even if I don't plan on writing unittests (lsUnit and lsMock anyone?) I found the recomendations quite usefull and will try to follow them in my programming.

Fun with events

Jan Schulz  |     |  Tags:  events lotusscript  |  Comments (0)
After seeing how much fun everybody else had with events, I tried to get my own version together.

I tried my luck with delegating the actual event handling to EventHandlers, which do the actual job. So the called QueryOpen Method looks like this:

Sub delegate_Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryopen(Source , Mode, Isnewdoc , Continue ) If Not Continue Then Exit Sub End Forall End Sub


The actuall handlers extend a basic EventHandler (which does nothing in each object method) with the required functionality. The rest of the design is modeled after the MVC Pattern as described in this presentation/example DB.

Fun: binding object methods to an event and not having the object around at "call time" (like when you forget to declare your object in Declaration and just do a <code>dim controller as New BaseDocumentController(source)</code> on the PostOpen-Event of the Form) will do funny things in the debugger: after the QueryClose Event the client goes into a endless loop. Without debugger, just nothing happens.

This is the complete code for Tims example:
Private Const ERR_ABSTRACT_INSTANTIATION = 1000 Private Const MSG_ABSTRACT_INSTANTIATION = | Attempt to instantiate as an object instance the abstract class | Public Class BaseDocumentController %REM The Controller Part of MVC Binds to a UIDocument, delegates all events to the EventHandlers %END REM Private fUIDoc As NotesUIDocument Private fModel As BaseDocumentModel Private fIsInitialized As Boolean Private listEventHandlers List As AbstractDocumentEventHandler Sub new(uidoc As NotesUIDocument) If Not uidoc Is Nothing Then Call initializeWithUIDoc(uidoc) End If End Sub Public Function getUIDoc() As NotesUIDocument Set getUIDoc = fUIDoc End Function Public Function initializeWithUIDoc(uidoc As NotesUIDocument) If uiDoc Is Nothing Then Error 1111, "Need a UI Document" End If Set fUIDoc = uidoc Set fModel = New BaseDocumentModel( fUIDoc.Document) fIsInitialized = True Call bindEvents() Call setupEventHandler() End Function %REM adds the default EventHandler (Validation) the to eventhandler List Can be overwritten to extend the EventHandler List, needs to call BaseDocumentController..setupEventHandler() to get the basic EventHandler setup or you need to add validation yourself %END REM Function setupEventHandler() Dim evHandler As New ValidationHandler(fModel) Call Me.addEventHandler( "validation", evHandler) Dim evHandler2 As New AuditTrailHandler(fModel) Call Me.addEventHandler( "AuditTrail", evHandler2) End Function Public Function addEventHandler(evName As String, evHandler As AbstractDocumentEventHandler) Set listEventHandlers(evName) = evHandler End Function Private Function bindEvents() On Event Queryopen From fUIDoc Call delegate_Queryopen On Event Postopen From fUIDoc Call delegate_Postopen On Event Querymodechange From fUIDoc Call delegate_Querymodechange On Event Postmodechange From fUIDoc Call delegate_Postmodechange On Event Queryrecalc From fUIDoc Call delegate_Queryrecalc On Event Postrecalc From fUIDoc Call delegate_Postrecalc On Event Querysave From fUIDoc Call delegate_Querysave On Event Postsave From fUIDoc Call delegate_Postsave On Event Querysend From fUIDoc Call delegate_Querysend On Event Postsend From fUIDoc Call delegate_Postsend On Event Queryclose From fUIDoc Call delegate_Queryclose End Function Sub delegate_Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryopen(Source , Mode, Isnewdoc , Continue ) If Not Continue Then Exit Sub End Forall End Sub Sub delegate_Postopen(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.PostOpen(Source) End Forall End Sub Private Sub delegate_Querymodechange(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querymodechange(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postmodechange(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postmodechange(Source) End Forall End Sub Private Sub delegate_Queryrecalc(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Queryrecalc(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postrecalc(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postrecalc(Source) End Forall End Sub Private Sub delegate_Querysave(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querysave(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postsave(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postsave(Source) End Forall End Sub Private Sub delegate_Querysend(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Call evHandler.Querysend(Source, Continue) If Not Continue Then Exit Sub End Forall End Sub Private Sub delegate_Postsend(Source As Notesuidocument) Forall evhandler In listEventHandlers Call evHandler.Postsend(Source) End Forall End Sub Private Sub delegate_Queryclose(Source As Notesuidocument, Continue As Variant) Forall evhandler In listEventHandlers Print "Calling eventhandler for QueryClose" Call evHandler.Queryclose(Source, Continue) If Not Continue Then Exit Sub End Forall Print "End QueryClose" End Sub End Class Class BaseDocumentModel %REM The model part of MVC Binds to the backend document and does validation (not implmented properly) and other stuff with the data %END REM Private fDoc As NotesDocument Private fIsInitialized As Boolean Sub new(doc As NotesDocument) If Not doc Is Nothing Then Call initializeWithDocument(doc) End If End Sub Function initializeWithDocument(doc As NotesDocument