2005 season finally here

Finally !! Some weather that resembles spring… Though not as warm as one could wish for (still only around 8 degress celsius) the weather turned sunny and it seemed as though everyone chose Easter as the time to start. The road along the coast northward from Copenhagen was “packed” with happy road bikers. Me and my big brother included.

For me it all started the night before because I still hadn’t changed back to my normal gearing (52/42/12) from my mountain gearing (52/39/13). I rode across the Pyrenees last summer and once I got back I never got around to changing the gearing back, but now I’m back and riding as never before.

For the last couple of months I have been training indoors (Spinning) riding steady at 75% -> 85% of my maximal heartrate and it seems to have worked for me. I feel stronger than normally starting out. Nice. The new Polar 725 watch I bought last year has been a real help since it allows me to monitor my training using procentages instead of the heartrate.

I also allows me to transfer the data to the computer where I keep a log of my training. Below is an example from my trip to the Pyrenees. The example shows three curves – one for heartrate, one for speed and a third for altitude.

Generic Lotus Notes export library

I finally got fed up writing ad-hoc export functions for applications so I have written a generic export library for Lotus Notes. The library is written in LotusScript and can be applied to any database.

Below I’ll discuss the concepts I have defined and show how the code is used.

The export library consists of four core concepts that translate into four core (abstract) classes:

  • OutputStream

    OutputStream models where the exported data is written to.
  • ExportSource

    ExportSource models a source of data for export.
  • ExportStrategy

    ExportStrategy models the way (e.g. CSV, XML) we write the exported data to the OutputStream.
  • ExportFormatter

    ExportFormatter models the way data from the NotesDocuments provided by the ExportSource is actually exported.

Below I’ll explain each concept in a little more detail.

OutputStream:

In the most simple form this is a thin wrapper around the built in NotesStream class. The class has been wrapped since it allows me to subclass OutputStream while keeping the classes that use OutputStream unaware of the differences. I have defined three sub-classes so far:

  • FileOutputStream (writes a a file on disk)
  • LineFileOutputStream (as above with each call to write() on a separate line – good for CSV exports)
  • RichTextOutputStream (writes to a NotesRichTextItem)

ExportSource:
At present I have two four implementations:

  • ViewExportSource (exports all the documents in a view)
  • CollectionExportSource (exports the documents in a NotesDocumentCollection)
  • SelectedExportSource (exports the documents the user has selected)
  • SearchExportSource (exports the documents found by the supplied @Formula query)

ExportStrategy:
The class is modelled on the Strategy pattern and I have defined three implementations so far:

  • CsvExportStrategy (exports the data in CSV format)
  • XmlExportStrategy (exports the data in XML format)
  • RssExportStrategy (exports the data in RSS format)

ExportFormatter:
I am a little unsure whether ExportFormatter is the correct name but here goes. I have defined two implementations so far:

  • ViewExportFormatter (exports the data as defined by a view in a database, that is reusing the @Formula for the columns)
  • ConfigurationExportFormatter (exports the data as defined by a set of configuration documents thus allowing the user to define his own export)

You combine the above concepts to export data. If you need to export the selected documents to a CSV-file using the format of the current view you would combine:

  • LineFileOutputStream pointing to a file on disk
  • SelectedExportSource to export the documents the use as selected
  • CsvExportStrategy to write the exported data as CSV
  • ViewExportFormatter to format the data as the current view

Example 1:
Export selected documents based on a view to a CSV-file on disk.

Option Public
Option Explicit
Use "CLASS: ExportSource"
Use "CLASS: OutputStream"
Use "CLASS: ExportFormatter"
Use "CLASS: ExportStrategy"

Sub Initialize
   'declarations
   Dim session As New NotesSession
   Dim db As NotesDatabase
   Dim view As NotesView
   Set db = session.GetDatabase("", "export_db.nsf")
   Set view = db.GetView("ExportDocuments")

   'we want to export selected documents only
   Dim es As New SelectedExportSource()

   'we want to format the documents as the current view (notesTestDocuments)
   Dim formatter As New ViewExportFormatter(Nothing, "notesTestDocuments", Nothing)

   'the documents should be written to a file replacing any existing file
   Dim out As New LineFileOutputStream("d:\export.csv", True)
   Call out.Open()

   'export the data as CSV to the created OutputStream and the ExportSource
   Dim strategy As New CsvExportStrategy(out, es)
   Call strategy.Export(formatter, True)

   'close the OutputStream
   Call out.Close()

End Sub

Example 2:
Export all documents in a view to a richtext item on a document as XML.

Option Public
Option Explicit
Use "CLASS: ExportSource"
Use "CLASS: OutputStream"
Use "CLASS: ExportFormatter"
Use "CLASS: ExportStrategy"

Sub Initialize
   'declarations
   Dim ws As New NotesUIWorkspace
   Dim session As New NotesSession
   Dim db As NotesDatabase
   Dim view As NotesView
   Dim doc As NotesDocument
   Dim rt As NotesRichTextItem

   'get current database and view
   Set db = session.CurrentDatabase
   Set view = ws.CurrentView.View

   'we want to export all documents in the current view
   Dim es As New ViewExportSource(view)

   'we want to format the documents as the current view (notesTestDocuments)
   Dim formatter As New ViewExportFormatter(view, "", Nothing)

   'the documents should be written to a rich text item
   Set doc = db.CreateDocument
   Call doc.ReplaceItemValue("Form", "TestDocument")
   Call doc.ReplaceItemValue("Title", "XML Export " + Now)
   Set rt = New NotesRichTextItem(doc, "Body")
   Dim out As New RichTextOutputStream(rt)
   Call out.Open()

   'export the data as XML to the created OutputStream and the ExportSource
   Dim strategy As New XmlExportStrategy(out, es)
   Call strategy.Export(formatter, True)

   'close the OutputStream
   Call out.Close()

   'save document
   Call doc.Save(True, False)

End Sub

Since some exports (i.e. selected documents as CSV to a file) are done quite frequently I have defined some helper classes (really based on the Facade pattern). These helper classes are called Exporters and wrap the above example 1 on one line of code:

Option Public
Option Explicit
Use "CLASS: Exporter"

Sub Initialize
   Dim exporter As New CsvExporter("d:\export.csv", "notesTestDocuments", True)
End Sub

LotusScript class: FileWriter for R6.x

Code:

'*** Constants ***
Private Const DEFAULT_CHARSET$ = "ISO-8859-1"

'/**
' * Base FileWriter class. Normal usage scenario would we:
' * 1. Dim a new FileWriter using the filename and whether to replace any existing file.
' * 2. (Optional) Set the character set to use if different from ISO-8859-1.
' * 3. Call the Open() method to open the FileWriter.
' * 4. Call the Write(Variant) method to write data to the file.
' * 5. Call the Close() method to close the FileWriter.
' *
' * @version 1.0 (16 December 2004)
' * @author lekkim@it-inspiration.dk
' * @author it-inspiration aps
' */
Public Class FileWriter
   'declarations
   Private pFilename As String
   Private pReplaceExisting As Boolean
   Private pStream As NotesStream
   Private pCharset As String

   '/**
   ' * Constructor.
   ' */
   Public Sub New(filename As String, replace_existing As Boolean)
      Me.pFilename = filename
      Me.pReplaceExisting = replace_existing
   End Sub

   '/**
   ' * Destructor.
   ' */
   Public Sub Delete()
      'make sure the stream has been closed
      Call Me.Close()
   End Sub

   '/**
   ' * Sets the charset to use.
   ' */
   Public Property Set Charset(charzet As String)
      Me.pCharset = charzet
   End Property

   '/**
   ' * Opens the file.
   ' */
   Public Sub Open()
      'declarations
      Dim session As New NotesSession
      Dim rc As Boolean

      'cannot open an open stream
      If Not (Me.pStream Is Nothing) Then
         Exit Sub
      End If

      'create stream
      Set Me.pStream = session.CreateStream()

      'should we replace ?
      If Me.pReplaceExisting Then
         'set up error handling
         On Error Goto catch_fileexist

         'does the file exist already
         If Filelen(Me.pFilename) Then
            'the file exists - delete it
            Kill Me.pFilename
         End If

         Goto finally_fileexist
catch_fileexist:
         'the file doesn't exist
         Resume finally_fileexist
finally_fileexist:
         'reset error handling
         On Error Goto 0
      End If

      'try and open the file
      If Me.pCharset  "" Then
         'use custom charset
         rc = Me.pStream.Open(Me.pFilename, Me.pCharset)
      Else
         'use default charset
         rc = Me.pStream.Open(Me.pFilename, DEFAULT_CHARSET)
      End If

      'did we open the file correctly ?
      If Not rc Then
         'could not open the file
         Error 9999, "Unable to open file: " + Me.pFilename
      End If

   End Sub

   '/**
   ' * Closes the file.
   ' */
   Public Sub Close()
      If Not (Me.pStream Is Nothing) Then
         Call Me.pStream.Close()'
         Set Me.pStream = Nothing
      End If
   End Sub

   '/**
   ' * Writes text to the writer.
   ' */
   Public Sub Write(buffer As Variant)
      'make sure the stream is open
      If Me.pStream Is Nothing Then
         Error 9999, "The FileWriter hasn't been opened yet or has been closed."
      End If

      If Typename(buffer) = "STRING" Then
         'write as text
         Call Me.pStream.WriteText(buffer)
      Else
         'write as bytes
         Call Me.pStream.Write(buffer)
      End If
   End Sub

End Class

Public Class LineFileWriter As FileWriter

   '/**
   ' * Constructor.
   ' */
   Public Sub New(filename As String, replace_existing As Boolean), FileWriter(filename, replace_existing)
   End Sub

   '/**
   ' * Overrides the parent implementation to make sure text is written as
   ' * lines. Also a check is made that only strings are written to to
   ' * the writer.
   ' *
   ' */
   Public Sub Write(buffer As Variant)
      'make sure the stream is open
      If Me.pStream Is Nothing Then
         Error 9999, "The FileWriter hasn't been opened yet or has been closed."
      End If

      If Typename(buffer) = "STRING" Then
         'write as text
         Call Me.pStream.WriteText(buffer, EOL_PLATFORM)
      Else
         'only allow text
         Error 9999, "You may only write strings."
      End If
   End Sub

End Class

LotusScript class: LocationDocument

Example – get info from current location document:

Dim l As New LocationDocument("")
Msgbox l.EmailAddress

Example – get info from named location document:

Dim l As New LocationDocument("Office")
Msgbox l.EmailAddress

Code:

'/**
' * LotusScript class for interacting with location documents
' * in the personal name and address book.
' */
Public Class LocationDocument
   'declarations
   Private pSession As NotesSession
   Private pDb As NotesDatabase
   Private pDoc As NotesDocument

   '/**
   ' * Constructor
   ' */
   Public Sub New(get_location As String)
      'declarations
      Dim viewNab As NotesView
      Dim nn As NotesName
      Dim ini_location As String
      Dim location As String
      Dim comma As Integer

      'get session
      Set Me.pSession = New NotesSession()

      If get_location = "" Then
         'get the location from notes.ini
         ini_location = Me.pSession.GetEnvironmentString("Location", True)

         'parse out the location name if none specifed in the constructor
         comma = Instr(1, ini_location, ",")
         location = Mid$(ini_location, 1, comma-1)
      Else
         'use supplied location name
         location = get_location
      End If

      'abbreviate the name
      Set nn = New NotesName(location)
      location = nn.Abbreviated

      'get database and view
      Set Me.pDb = Me.pSession.GetDatabase("", "names.nsf")
      Set viewNab = Me.pDb.GetView("Locations")

      'lookup location document
      Set Me.pDoc = viewNab.GetDocumentByKey(location, True)
      If Me.pDoc Is Nothing Then
         Error 9999, "Location document not found in names.nsf"
      End If
   End Sub

   '/**
   ' * Saves.
   ' */
   Public Sub Save()
      Call Me.pDoc.Save(False, False)
   End Sub

   '/**
   ' * Property to get the backend document.
   ' */
   Public Property Get Document As NotesDocument
      Set Document = Me.pDoc
   End Property

   '/**
   ' * Property to get the e-mail address.
   ' */
   Public Property Get EmailAddress As String
      EmailAddress = Me.pDoc.GetItemValue("ImailAddress")(0)
   End Property

   '/**
   ' * Returns the name of the directory server.
   ' */
   Public Property Get DirectoryServer As NotesName
      Dim nn As New NotesName(Me.pDoc.GetItemValue("DirectoryServer")(0))
      Set DirectoryServer = nn
   End Property

   '/**
   ' * Sets the name of the directory server.
   ' */
   Public Property Set DirectoryServer As NotesName
      Call Me.pDoc.ReplaceItemValue("DirectoryServer", DirectoryServer.Canonical)
   End Property

   '/**
   ' * Returns the name of the Sametime server.
   ' */
   Public Property Get SametimeServer As NotesName
      Set SametimeServer = New NotesName(Me.pDoc.GetItemValue("SametimeServer")(0))
   End Property

   '/**
   ' * Sets the name of the Sametime server.
   ' */
   Public Property Set SametimeServer As NotesName
      Call Me.pDoc.ReplaceItemValue("SametimeServer", SametimeServer.Canonical)
   End Property
End Class

SPOC Utility Agents

Copy companies:

Sub Initialize
   Dim s As New NotesSession
   Dim dbTarget As NotesDatabase
   Dim dbSource As NotesDatabase
   Dim docSource As NotesDocument
   Dim docTarget As NotesDocument
   Dim viewSource As NotesView
   Dim viewTarget As NotesView
   Dim copy_count As Integer

   Set dbSource = s.CurrentDatabase
   Set dbTarget = s.GetDatabase("server", "spoc.nsf")
   Set viewTarget = dbTarget.GetView("lookupMainFirmById")
   Set viewSource = dbSource.GetView("lookupMainFirmById")
   viewTarget.AutoUpdate = False
   viewSource.AutoUpdate = False

   Set docSource = viewSource.GetFirstDocument
   While Not (docSource Is Nothing)
      Set docTarget = viewTarget.GetDocumentByKey(docSource.ID(0), True)
      If docTarget Is Nothing Then
         copy_count = copy_count + 1
         Call docSource.CopyToDatabase(dbTarget)
      End If

      Set docSource = viewSource.GetNextDocument(docSource)
   Wend

   Msgbox "Copy count=" & copy_count
End Sub

Copy contacts:

Sub Initialize
   Dim s As New NotesSession
   Dim dbTarget As NotesDatabase
   Dim dbSource As NotesDatabase
   Dim docSource As NotesDocument
   Dim docTarget As NotesDocument
   Dim viewSource As NotesView
   Dim viewTarget As NotesView
   Dim copy_count As Integer

   Set dbSource = s.CurrentDatabase
   Set dbTarget = s.GetDatabase("server", "spoc.nsf")
   Set viewTarget = dbTarget.GetView("lookupContactsByUnique")
   Set viewSource = dbSource.GetView("lookupContactsByUnique")
   viewTarget.AutoUpdate = False
   viewSource.AutoUpdate = False

   Set docSource = viewSource.GetFirstDocument
   While Not (docSource Is Nothing)
      Set docTarget = viewTarget.GetDocumentByKey(docSource.ContactUnique(0), True)
      If docTarget Is Nothing Then
         copy_count = copy_count + 1
         Call docSource.CopyToDatabase(dbTarget)
      End If

      Set docSource = viewSource.GetNextDocument(docSource)
   Wend

   Msgbox "Copy count=" & copy_count
End Sub

Copy e-mails:

Sub Initialize
   Dim s As New NotesSession
   Dim dbTarget As NotesDatabase
   Dim dbSource As NotesDatabase
   Dim docSource As NotesDocument
   Dim docTarget As NotesDocument
   Dim viewSource As NotesView
   Dim viewTarget As NotesView
   Dim copy_count As Integer
   Dim docEmailSource As NotesDocument
   Dim docEmailTarget As NotesDocument
   Dim dc As NotesDocumentCollection

   Set dbSource = s.CurrentDatabase
   Set dbTarget = s.GetDatabase("server", "spoc.nsf")
   Set viewTarget = dbTarget.GetView("lookupContactsByUnique")
   Set viewSource = dbSource.GetView("lookupContactsByUnique")
   viewTarget.AutoUpdate = False
   viewSource.AutoUpdate = False

   Set docSource = viewSource.GetFirstDocument
   While Not (docSource Is Nothing)
      'find contact in target spoc
      Set docTarget = viewTarget.GetDocumentByKey(docSource.ContactUnique(0), True)

      'get responses
      Set dc = docSource.Responses
      Set docEmailSource = dc.GetFirstDocument
      While Not (docEmailSource Is Nothing)
         'copy
         Set docEmailTarget = docEmailSource.CopyToDatabase(dbTarget)
         Call docEmailTarget.Save(True, False)

         'make response
         Call docEmailTarget.MakeResponse(docTarget)

         'increment count
         copy_count = copy_count + 1

         'get next
         Set docEmailSource = dc.GetNextDocument(docEmailSource)
      Wend

      'get next
      Set docSource = viewSource.GetNextDocument(docSource)
   Wend

   Msgbox "Copy count=" & copy_count
End Sub

Disable auto-commit in DB2 from CLI

Auto-commit is by default enabled in the command line interface (CLI) of DB2. To disable auto-commit use the UPDATE COMMAND OPTIONS command:

update command options using c off
insert into some_table values (1, 'abc123');
commit

NABPerson

Example:

Dim n As New NABPerson()
Msgbox n.Shortname
Const NAB_FILENAME$ = "names.nsf"
Const DEBUG_USERNAME$ = "DEBUG_SCANNING_USER"

Public Class NABPerson
   'declarations
   Private pSession As NotesSession
   Private pDb As NotesDatabase
   Private pDoc As NotesDocument

   '/**
   ' * Constructor.
   ' */
   Public Sub New()
      Set Me.pSession = New NotesSession
      Dim view As NotesView
      Dim server As String
      Dim debug_user As String

      'get the users home server
      server = Me.pSession.GetEnvironmentString("MailServer", True)

      'get database
      Set Me.pDb = Me.pSession.GetDatabase(server, NAB_FILENAME)

      'get view
      Set view = Me.pDb.GetView("($Users)")

      'should we use a debug name
      debug_user = Me.pSession.GetEnvironmentString(DEBUG_USERNAME)
      If debug_user  "" Then
         'use debug name
         Set Me.pDoc = view.GetDocumentByKey(debug_user, True)
      Else
         'use active usename
         Set Me.pDoc = view.GetDocumentByKey(Me.pSession.Username, True)
      End If

      'verify that we found a document
      If Me.pDoc Is Nothing Then
         If debug_user  "" Then
            Error 9999, "User (" + debug_user + ") not found in Domino Directory on " + server
         Else
            Error 9999, "User (" + Me.pSession.Username + ") not found in Domino Directory on " + server
         End If
      End If
   End Sub

   '/**
   ' * Get the shortname of the user.
   ' */
   Public Property Get Shortname As String
      Shortname = Me.pDoc.Shortname(0)
   End Property

End Class

LotusScript class: FileWriter for R5.x

Example:

Dim fw As New FileWriter(|c:lekkim.txt|, True)
Call fw.WriteText(CStr(Now))
Call fw.Close()

Code:

'constants
Const FILE_NOTOPENED% = 0
Const FILE_CLOSED% = -1

Public Class FileWriter
   'declarations
   Private pFileNum As Integer
   Private pFilename As String

   '/**
   ' * Constructor.
   ' */
   Public Sub New(filename As String, replace_existing As Integer)
      'make sure we have a filename
      If filename = "" Then
         Error 9999, "You must supply a filename."
      End If

      'store filename
      Me.pFilename = filename

      'error handling
      On Error Goto catch

      'should we replace
      If replace_existing Then
         'remove the file if it exists
         Kill filename
      End If

      'exit gracefully
      Exit Sub

catch:
      Resume finally
finally:
      Exit Sub
   End Sub

   '/**
   ' * Destructor.
   ' */
   Public Sub Delete()
      'make sure we close any file we might have open
      If Me.pFileNum > 0 Then
         'we need to close the file
         Close Me.pFileNum
      End If
   End Sub

   '/**
   ' * Write text to the file.
   ' */
   Public Sub WriteText(text As String)
      'is the file open
      If Me.pFileNum = FILE_NOTOPENED Then
         'no - open the file

         'get a new filenumber
         Me.pFileNum = Freefile()

         'open the file for writing
         Open Me.pFilename For Output As Me.pFilenum
      Elseif Me.pFileNum = FILE_CLOSED Then
         'the user is trying to write to a closed file
         Error 9999, "You cannot write to a file you have closed"
      End If

      'write to the file
      Print #Me.pFileNum, text
   End Sub

   '/**
   ' * Close the file.
   ' */
   Public Sub Close()
      'close any file we might have open
      If Me.pFileNum > 0 Then
         'we need to close the file
         Close Me.pFileNum

         'set filenum to -1 to signal that we closed the file
         Me.pFilenum = FILE_CLOSED
      End If
   End Sub

End Class