'*** Constants *** Private Const ITEM_MAX_SIZE = 31000 Public Class NotesItemWrapper 'declarations Private pDoc As NotesDocument Private pCurrentItem As NotesItem Private pItemNames As Variant Private pItemIndex As Integer '/** ' * Constructor. ' */ Public Sub New(doc As NotesDocument, item_names As Variant, append_data As Boolean) 'declarations Dim i As Integer 'store the document Set Me.pDoc = doc 'item_names must be an array of strings or a item Forall n In item_names If Typename(n) "STRING" Then Error 9999, "The supplied item names but be a string or an array of strings." End If End Forall 'make sure the item names are an array If Not Isarray(item_names) Then Dim v(0) As String v(0) = Cstr(item_names) Me.pItemNames = v Else 'store the item names to use Me.pItemNames = item_names End If 'get the first item to use If Not append_data Then 'remove all the items with the supplied names and recreate the first one Forall n In Me.pItemNames Call Me.pDoc.RemoveItem(n) End Forall 'recreate the first item Set Me.pCurrentItem = New NotesItem(Me.pDoc, Me.pItemNames(Me.pItemIndex), "") Else 'find the first item to use For i=0 To Ubound(Me.pItemNames) 'get the first item Set Me.pCurrentItem = Me.pDoc.GetFirstItem(Me.pItemNames(Me.pItemIndex)) 'is this item below the threshold If Lenb(Me.pCurrentItem.Text) < ITEM_MAX_SIZE Then 'use this item Me.pItemIndex = i Exit For End If Next End If End Sub '/** ' * Appends text to the item checking whether a new item ' * is required to hold the text. If no more fields are ' * available we return an Error 9999. ' */ Public Sub AppendText(data As String) 'check whether there is space for the data If Me.pCheckSize(data) Then 'it is ok - append Me.pCurrentItem.Text = Me.pCurrentItem.Text + data Else 'raise error Error 9999, "Not enought space to hold the data." End If End Sub '/** ' * Appends a multi-value item to the item(s). If no more space ' * is available we return an Error 9999. ' */ Public Sub AppendToTextList(data As String) 'check whether there is space for the data If Me.pCheckSize(data) Then 'it is ok - append Call Me.pCurrentItem.AppendToTextList(data) Else 'raise error Error 9999, "Not enought space to hold the data." End If End Sub '***************************************************************************** '* Private methods '***************************************************************************** '/** ' * Checks the size of the current item. Returns True if the item ' * can hold the supplied data. ' */ Private Function pCheckSize(data As String) As Boolean 'declarations Dim size As Long 'calculate the item size size = Lenb(Me.pCurrentItem.Text) + Lenb(data) 'Print "Size is: " & size & " (index = " & Me.pItemIndex & ", " & Me.pCurrentItem.Name & ")" 'decide what to do If size < ITEM_MAX_SIZE Then 'item can hold the byte count pCheckSize = True Else 'no can do - try and get the next item If Me.pItemIndex <= Ubound(Me.pItemNames) Then 'get the next item Me.pItemIndex = Me.pItemIndex + 1 Set Me.pCurrentItem = New NotesItem(Me.pDoc, Me.pItemNames(Me.pItemIndex), "") 'return true pCheckSize = True Else 'no more items pCheckSize = False End If End If End Function End Class
Thought no one was using mod_jk2 anymore…
…but I appear to be mistaken. I am very confused about the availability and the differences between the different mod_jk
* modules. I will have to look into it.
This article explains how to use mod_jk2 under Apache to cluster multiple Tomcat instances behind an Apache web server.
StringTokenizer
I just can’t believe this kind of functionality isn’t built in but so it is. The code should be pretty straight forward and well commented.
A StringTokenizer is used to split a string of text up into smaller bits delimeted by the chosen delimeter. A StringTokenizer really shines when it comes to parsing comma-separated files or the like.
Feel free to use the code but please let me know if you do. The code is provided as-is and I accept no responsibility what so ever.
'*** Constants ***
Private Const STANDARD_DELIMETER$ = ";"
'/**
' * StringTokenizer class to help break a string into tokens. A token is the
' * string between delimeters (defaults to semi-colon). The class handles
' * lines ending in a delimeter if you set the EndingDelimeter property
' * to True.
' *
' * Standard usage:
' * 1. Dim a new StringTokenizer using the source to parse as an argument.
' * 2. (Optional) Change the delimeter using the Delimter property.
' * 3. (Optional) Change the EndingDelimeter property.
' * 4. Call the HasNextElement() method and the NextElement() functions to
' * loop the string.
' *
' * Example 1:
' * Dim t1 As New StringTokenizer(";123;456;789;")
' * While t1.HasNextElement()
' * Print t1.NextElement()
' * Wend
' *
' * Output:
' * -
' * - 123
' * - 456
' * - 789
' * -
' *
' * Example 2:
' * Dim t2 As New StringTokenizer(";123;456;789;")
' * t2.EndingDelimeter = True
' * While t2.HasNextElement()
' * Print t2.NextElement()
' * Wend
' *
' * Output:
' * -
' * - 123
' * - 456
' * - 789
' *
' * Example 3:
' * Dim t3 As New StringTokenizer("123;456%789;012")
' * t3.Delimeter = "%"
' * While t3.HasNextElement()
' * Print t3.NextElement()
' * Wend
' *
' * Output:
' * - 123;456
' * - 789;012
' *
' * @version 1.0 (16 December 2004)
' * @author lekkim@it-inspiration.dk
' * @author it-inspiration aps
' */
Public Class StringTokenizer
'declarations
Private pSource As String
Private pDelimeter As String
Private pStart As Long
Private pDelim As Long
Private pEndingDelimeter As Boolean
Public Sub New(source As String)
Me.pSource = source
Me.pStart = 0
Me.pDelim = 0
Me.pEndingDelimeter = False
Me.pDelimeter = STANDARD_DELIMETER
End Sub
Public Property Set EndingDelimeter As Boolean
Me.pEndingDelimeter = EndingDelimeter
End Property
Public Property Set Delimeter As String
Me.pDelimeter = Left(Delimeter, 1)
End Property
Public Function HasNextElement() As Boolean
'declarations
Dim index As Long
'does the source have contents
If Len(Me.pSource) = 0 Then
'nope - return false
HasNextElement = False
Exit Function
End If
'have we been looking for a delimeter before ?
If Me.pStart = 0 And Me.pDelim = 0 Then
'nope - does the source start with a delimeter ?
If Left(Me.pSource, 1) = Me.pDelimeter Then
'the first element is empty
Me.pDelim = 1
'set start
Me.pStart = 1
Else
'find the first delimeter
index = Instr(1, Me.pSource, Me.pDelimeter)
'did we find a delimeter
If index > 1 Then
'yes we did - set the index of the delimeter
Me.pDelim = index
'set start
Me.pStart = 1
Else
'no we didn't only one element
Me.pDelim = 0
Me.pStart = 1
End If
End If
'return true
HasNextElement = True
Exit Function
Else
'we have been looking before - move the pointers
Me.pStart = Me.pDelim + 1
'have we reached the end of the source string ?
If Me.pStart > Len(Me.pSource) Then
'yes we have - return false
HasNextElement = False
Exit Function
Elseif Me.pStart = Len(Me.pSource) Then
'see if the last character is a delimeter
If Right(Me.pSource, 1) = Me.pDelimeter Then
'the last character is a delimeter - should the class add
'an empty element at the end
If Me.pEndingDelimeter Then
'the lines end in a delimeter
HasNextElement = False
Else
'we should signal an empty element
Me.pDelim = Me.pStart
HasNextElement = True
End If
Else
'the is a one character element at the end
Me.pStart = Len(Me.pSource)
Me.pDelim = 0
HasNextElement = True
End If
Else
'just look for the next delimeter
index = Instr(Me.pStart, Me.pSource, Me.pDelimeter)
'did we find a delimeter
If index > Me.pStart Then
'we found a delimter
Me.pDelim = index
HasNextElement = True
Elseif index = 0 And Me.pStart < Len(Me.pSource) Then
'there is one more element
Me.pDelim = Len(Me.pSource)
HasNextElement = True
Else
'no more delimeters
HasNextElement = False
End If
End If
End If
End Function
Public Function NextElement() As String
'if the delimeter is 0 there is only one element to return
If Me.pDelim = 0 Then
NextElement = Mid(Me.pSource, Me.pStart)
Exit Function
Elseif Me.pDelim = Me.pStart Then
'return an empty element
NextElement = ""
Exit Function
Elseif Me.pDelim = Len(Me.pSource) Then
'does the source end with a delimeter
If Me.pEndingDelimeter Then
'the line is supposed to end with a delimeter so we remove it
NextElement = Mid(Me.pSource, Me.pStart, Len(Me.pSource) - Me.pStart)
Else
'the line should not end with a delimeter so if it does so we need to
'return the rest of the line minus 1
If Right(Me.pSource, 1) = Me.pDelimeter Then
NextElement = Mid(Me.pSource, Me.pStart, Len(Me.pSource) - Me.pStart)
Me.pDelim = Me.pDelim - 1
Else
NextElement = Mid(Me.pSource, Me.pStart)
End If
End If
Else
NextElement = Mid(Me.pSource, Me.pStart, Me.pDelim-Me.pStart)
End If
End Function
End Class
Google suggests
Google suggests looks to me to what we really need in our application. in this particular application the user has to select a street from the database without resorting to dropdowns and the like. At the moment this is a accomplished by the user specifying the start of the street name, clicks a button (or presses enter) and a asynchronenous XML query is run the background. This works perfectly but having something like Google suggests would surely help users.
The main issue with the way it currently works is that users need to know the exact spelling of the street (or at least parts of the exact spelling) to find anything. Using something like Google suggests has users could have some help finding the street without needing to know the spelling.
This guy dissected the code and made it more readable.
Thinking about LotusScript
Lately I have been help thinking about the LotusScript language and why it hasn’t matured more over the years. I have two primary grievances:
- The lack of OO support in the IDE.
- The lack of helper classes in the API.
The lack of OO support in the IDE
Come on Lotus – when will this happen. How come it is practically easier to develop LotusScript classes in Visual Studio than in Domino Designer. Please step up and add class recognizion to the LotusScript IDE. It is practially imposible to handle classes in Script Libraries and in Agents.
What would be really help would be a simple tree structure showing the class hierarchy in the left hand side – it anything else fails let us have the same UI as in the Java IDE… 🙂
I think it is a real show stopper for the adaption of OO design and programming since programmers new to OOP loose track of the code and the structure. The overview is much better when you use the traditional procedural approach.
The lack of helper classes in the API
I know additional classes has been added in version 6 but how come no collection api or more general string operation classes has been added.
There is a major need for a standardized collection API to help the adoption of OOP. Just see what the Collection API did for Java 2.
AMgr memory consumption part 2
After talking to Lotus Support and monitoring the server more closely it appears not to be the AMgr alone having the problem. The problem appears to be more general. Yesterday afternoon the CA task was at 200 MB memory! An important task but not one one of the busiest I must say…
Number of Lotus Notes seats
Hi Mikkel -- Both vendors have generally gotten away from making seat counts the focus. I think Microsoft is claiming 130 million, and IBM claims about 113 million. The problem with seat counts is that they are completely artificial... unaudited numbers. In terms of numbers to bet on, you could do well with the Gartner market share numbers. They report IBM as having 46% share, Microsoft at 44.2%. These percentages are based on revenue, not seats. Those numbers are reported publicly at http://www.cmpnetasia.com/ViewArt.cfm?Artid=24452&Catid=8&subcat=83 Hope this helps. --Ed
Using Velocity in an agent
At work I am using Velocity in a number of web applications and was looking into using it for a newsletter application we have. This would allow authors to write more dynamic newsletters, and insert user-specific information in the subject and body of the newsletter.
I already had an implementation in Java that would replace macros using String operations. While the current solution works flawlessly but I was looking for support for if’s statements etc. which Velocity has.
I turned out that replacing my own implementation was a matter of replacing the Decorator implementation I was using with a new one using Velocity:
import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import dk.itinspiration.newsletter.*;
public class VelocityMacroDecorator extends Decorator {
public void decorate(Message msg) {
try {
// get the intended recipient
Person p = msg.getRecipient();
// get newsletter contents
String content = msg.getContent();
// initialize Velocity
Velocity.init();
// create and populate context
VelocityContext ctx = new VelocityContext();
ctx.put("email", p.getEmail());
ctx.put("firstname", p.getFirstname());
ctx.put("lastname", p.getLastname());
ctx.put("password", p.getPassword());
// create a string writer for the result
StringWriter sw = new StringWriter();
// replace macros
Velocity.evaluate(ctx, sw, null, content);
// set email contents
msg.setContent(sw.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
This’s it! Surely very simple. Now it is just a matter of populating the VelocityContext with more information from the Person object (the newsletter subscriber) and about the newsletter in general (number of subscribers etc.).
The only caveat is that you must allow the agent to run with restricted operations due to Velocity requesting information about the system (dangerous stuff such as line separator and the like).
A sample newsletter could now be something like the following:
Hello $firstname $lastname You are subscribed using '$email' #if ($password != "") and your password is '$password'. #else . #end
gzip’ing Tomcat under Apache
Excellent tip/howto on caching and/or gzip’ing pages from Java servers (e.g. Tomcat) running behind an Apache server.
HTTP compression filter on servlets : good idea, wrong layer
AMgr memory consumption
We recently installed a new Domino 6.5.2 server at a customer site. The server was herafter downgraded to 6.5.1 and a Sametime 6.5.1 server installed on top since the customer wanted to run Sametime as well. This was all very good and was pretty straight forward – no worries there.
The server should support 8 concurrent users and one would think that the HP monster server with 2GHz CPU and 1GB memory running Windows 2003 Server their IT guy got should be able to support this no problem but alas…
After a couple of days we began having complaints as to the speed of the server so we went to take a look. When looking at the task manager the AMgr, Sametime and HTTP tasks were all consuming 250-350 MB of memory and the total memory consuption was 1,1 GB. The server has swapping from the start. Once restarted the tasks would start and start consuming memory from the get go. The AMgr has 2 or 3 scheduled agents and one “After mail arrives” agent scheduled so it couldn’t be the actual load.
I created a support ticket with Lotus Support yesterday night and at 10 o’today I had my local support techie on the phone. Excellent response time! He started out with a couple of questions regarding the agent manager but also quickly concluded that it wasn’t that. He sent me a list of things he would like me to do:
- Enable console logging (start colsolelog)
- Enable thread id debugging (set config debug_threadid=1)
- Restart the server
- Do a memory “dump” every 5 minutes until the server reaches peak memory load (load nserver -mem)
- Do a NSD dump.
- Obtain a copy of the notes.ini and names.nsf
- Dump it all in a zip-file and upload it to the support FTP server so it can be sent of the Dublin (location of the more hardcore guys).
I will have to do all this tomorrow morning so I get to go to the office a little early. There is a catch though – how to issue a command in the server console every 5 minutes without actually sitting there with a watch timing the damn thing ?
I’m going to use the Domino Console (the java client that connects to a management layer beneat the Domino Server) – yes finally found a way to use it. The Domino Console has a functionality where it can issue periodic server console commands. Took a look at it and it will suit my needs just fine.
Well I’m of to bed – I have an early start tomorrow.