Apr 28

LS2J Error: Threw java.lang.OutOfMemoryError

I have been working on a project that moves data to and from LDAP using JNDI and Java. I have built document based LEI type engine using LotusScript and wanted to take advantage of it but had to use Java talk to LDAP. The best fit seemed LS2J, based Julian’s LS2J example everything looked great. All the testing was going great both updating and reading LDAP. Then I started receiving LS2J Error: Threw java.lang.OutOfMemoryError. Having written Java code in Domino before I went through my code making sure that everything was set to null; when I was done. Still the problem existed.

The SPR description describes the problem perfectly:

Fix for a memory leak when passing strings from Java back to LotusScript, which had caused LS2J Error: Threw java.lang.OutOfMemoryError.

It looks like we will be upgrading to 6.5.3 or higher.

Apr 26

Domino Environment Part II: Library Templates

With an understanding of why templates should be used for database development from . Lets take a look at what other benefits there are to developing in templates. I use two different types of templates: library & design. Both of these templates never migrate from the development environment. The library will be for reusable code, and the design will be used with third party applications. I will save the design template for Part III. Rule #1: Never use Prohibit design refresh in a template (See ). Rule #2: Use Design inheritance to maximize code reuse. Rule #3: Database inheritance should only occur with databases, except for third party package enhancements. (Part III)  

Prohibit design refresh

The only place that I have found value with this setting is in databases. The best example is your mail database each folder you created has prohibit design refresh set otherwise the folder would be deleted at night when the design task runs on the server. This same setting can be used in applications for views, agents or any other design elements. Prohibit design refresh can actually be a hindrance even in a database, because the design never can be updated. Lotus realized this during upgrades of mail databases, so they added a new formula option to update a folder based on a different view. Check out the ‘Upgrade Folder Design’ agent in the Mail 6 template, or ‘Upgrade this folder or view to R5 design’ in the Mail 5 template. Any time a new design element is created with the ‘Inherit from Master Template’ set, the prohibit design refresh will automatically be checked. So if a new agent, form or view was created in your mail database it would have the prohibit design refresh enabled. This prevents it from getting deleted when the design task runs.

Design element inheritance

Inheritance in Domino follows the same concepts as OO. In LotusScript (which has OO characteristics) classes can inherit from another class which is like database inheritance. Design element inheritance resembles the method or property of a class, because they can be overridden or defined differently in each class. Following the same concept of other OO based languages lets start building a framework or library for the Notes/Domino environment. The library will reside in a template, there could be multiple templates (Notes, Domino, web, admin, mobile). Don’t get to detail with the library templates they will be hard to maintain. These library templates will hold reusable design elements like subform footers, script libraries, an all documents view, or a hybrid of design elements.

Reusable Footer Example

The footer contains the date and time of when someone created and last updated a document. This subform should always inherit so fixes and new features can easily be implemented. Let’s assume the subform is in the ‘Domino Library Template’ with the ‘Master template’ property set to ‘Domino Library Template’.

Implementation

  1. Open the ‘Domino Library Template’
  2. Select the subform ‘User Info Footer’
  3. Select Edit-Copy from the menu bar
  4. Open the destination template
  5. Select Edit-Paste from the menu bar.
  6. Select ‘Yes’ when prompted to inherit future changes
That is 6 more steps than I want to do and this is simple. When there are multiple design elements that are needed the task can become tedious and error prone. Each design elements need to be selected based on the design type and then repeat steps 3-6 for each. To easy the pain copy and pasting the design elements I created a tool to accomplish this. Quick Elementer will group multiple design elements and allow the implementation of the design elements in a click or two. It is very much like the group objects in Teamstudio’s Design Manager (formally Teamstudio Librarian). It will also set the inherit from Master Template property automatically. Hopefully Part II will not take me three months to write. Don’t forget to check out Quick Elementer
Apr 26

Quick Elementer

Updated version of is now available. Quick Elementer is a document based design element implementer. It is very much like the group objects in Teamstudio’s Design Manager (formally Teamstudio Librarian). Quick Element Library (4) will perform the task of coping and pasting design elements form one database to another. I created this because I am a big believer in code reuse using templates and got tired of copying and pasting! Some of my modules (groups of design elements that work together to perform a function) required more than just a script library or a view. Some required 2 script libraries, 3 views, a form and an agent. With my forgetfulness I would spend wasted time trying to figure out why my module would not work that wored in other applications. The Quick Elementer form A Quick Elementer allows the selecting of multiple design elements based on what is in the database. The inherit design field will set the design element’s ‘Master Template’ field to the master template name of the current database. You can sort the design elements base on the type of design or the design element name. The ‘Copy Elementer to Database’ will prompt for a destination database and copy the design elements. The Quick Elementer view The view lists the design elements that are apart of the module and if the future design changes will be inherited. The ‘Copy Elementer to Database’ will prompt for a destination database and copy the design elements. Copy Elementer to Database When all the design elements have been copied to the destination database Elementer will ask if the destination database should be opened. Quick Element Library (4)
Apr 25

Debug LotusScript For

Debug LotusScript ForI think every developer has been irritated by opening there calendar when debugging LotusScript. I have not counted but there is about 10-15 times that continue button needs to be pressed. Then there is another 5-10 times when closing the calendar entry. This is not counting all the continues when reading mail.

What I would like to have is a list of database that I can choose to debug. This screen would replace the non value prompt letting me know that the debug is turned on or off. There are four sections to help control which database should be debugged and which should never be debugged.

  • Database Being Debugged would contain all the databases that could be debugged.
  • Currently Opened Databases would allow the quick selecting of applications to add them to the databases to be debugged. Even though the database is being debugged it could also appear in the currently opened database because it is opened.
  • Previously Debugged Databases would contain the last 5-10 databases that were debugged to quickly re-add them to the debug list.
  • Never Debug these databases would override the debug all databases but not the debug list.

My favorite feature is the list of databases that would never be debugged. There would still be the feature to debug all databases, but I don’t see that being used much anymore. Then I would never have to debug my mail or calendar again.
Finally the ‘Add Database’ button would allow the selecting of a new database.

So what do you think?
Apr 21

Teamstudio Script Browser 1.02

Teamstudio Script Browser 1.02 is here. Teamstudio must have had one ear open to Andrew Pollack suggestions because the ability to resize is now available. The ‘windowshade’ is really nice. New features in 1.02
  • Window is now re-sizeable
  • Window shade mode (double click in caption bar) now reacts to mouseover events. The Script Browser window expands when mouse is over the window, and contracts when the mouse leaves.
Fixes in 1.02
  • The Toolbar icon installs more efficiently.
  • Goto Definition now selects the correct function on R6.0.x
  • References feature corrected to find all occurrences of a function.
Check out the database title it is a new developer tool I am working on. It assist in copying reusable design elements in to templates.
Apr 17

Edit Document Fields 5.0

Updated version of Edit Document Fields is now available. Edit Document Fields 4.2 version that I had planned grew and took a little longer than expected. This is good news! Lets take a look at what is new:
  • Use profile document to store the last field edited per form
  • Use profile document to store the last data type and formula for each field
  • Added new data type of Formula, thanks Lefty Tsamis for the work and idea
  • Added new data type of Password Convert, Password is now Password Set
  • Switched from UNID to NoteID to allow 500 documents to be updated
  • Two pass – gathers the documents before any documents are updated, eliminates updated documents twice and ensures all documents are processed
  • Use of Eval function instead of checking the datatype for every document, thanks Lefty Tsamis again
Performance gain of the Eval was off set between with the two pass process. The same flow is intact so just download all the code and start editing fields. If you would like to step threw the code break down then continue.

The 5 Simple Steps

  1. Select the document(s) to update
  2. Click your toolbar button
  3. Select the field to update
  4. Select the data type or action to be performed
  5. Enter the new information (if prompted)

Limitations

  • Only 500 documents can be updated at a time
  • Field value is limited to 255 because of the @Prompt limitations
  • When updating multiple documents only the documents that are selected and visible (expanded) in the view will be updated. Notes needs to be able to navigate to the document

Code Break Down

Constants

REM {Edit Document Fields 5.0 by Chad Schelfhout.};
REM {Visit http://www.chadsmiley.com/EditDocumentFields for the latest updates};
 
REM {Constants};
EditLastField:="edfLastField";
EditLastFieldDataType:="edfLastFieldDataType";
EditLastSeparator:="%~%";
ProfileName:="ChadSmiley Tools";
PromptTitle:=@DbTitle+" - "+@ViewTitle;
MaxSearchForSelectedDocs:=5520;
MaxUpdatedDocuments:=1000;
Arrayseparator:=";";
NoteEntryLength:=11;
PormptNewLine:=@Char(13)+@Char(13);

Data Types and Data Type Code

This defines all the possible datatypes and the formula that will be used to set the field.
REM {Data types|@Function execution};
DataTypesCombo:=@Explode(
"Integer|@TextToNumber(RawValue)$"+
"Integer Multi Value|@TextToNumber( @Explode( RawValue;Separator; @True))$"+
"Date|@ToTime(RawValue)$"+
"Date Multi Value|@ToTime( @Explode( RawValue;Separator; @True))$"+
"Text|@Text(RawValue )$"+
"Text Multi Value|@Text(@Explode(RawValue;Separator; @True))$"+
"Name|RawValue$"+
"Name Multi Value|RawValue$"+
"Common Name|@Name( [CN]; RawValue )$"+
"CommonName Multi Value|@Name( [CN]; @Explode( RawValue;\":\"; @True ) )$"+
"Upper Case Text|@UpperCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
"Lower Case Text|@LowerCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
"Proper Case Text|@ProperCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
"Upper Case Text Multi Value|@UpperCase( @Explode( @Text( @GetField( EditField[ef] ) );Separator; @True ))$"+
"Lower Case Text Multi Value|@LowerCase( @Explode( @Text( @GetField( EditField[ef] ) );Separator; @True) )$"+
"Proper Case Text Multi Value|@ProperCase( @Explode( @Text( @GetField( EditField[ef] ) );Separator; @True) )$"+
"Replace Substring|@ReplaceSubstring( @GetField( EditField[ef] ); FromRawValue;RawValue )$"+
"Replace|@Explode( @Replace( @GetField( EditField[ef] );FromRawValue;RawValue );Separator;@True )$"+
"Implode|@Implode( @Text( @GetField( EditField[ef] ) );Separator )$"+
"Explode|@Explode( @Text( @GetField( EditField[ef] ) );Separator; @True )$"+
"Formula|@Eval( RawValue )$"+
"Abbreviate Name|@Name([Abbreviate]; RawValue )$"+
"Abbreviate Name Multi Value|@Name( [Abbreviate]; @Explode( RawValue;Separator; @True ) )$"+
"Password Set|@Password( RawValue )$"+
"Password Convert|@Password( @GetField( EditField[ef] ) )$"+
"Remove Field|@DeleteField$"+
"Unique|@Unique(@GetField( EditField[ef]))$"+
"+ Append Values|@If(" +
"   @GetField(EditField[ef])=\"\"; RawValue;" +
"   @Contains(DefaultDataType; \"Date\");" +
"      @If( @IsError( @ToTime( RawValue ) ) ;" +
"          \"\" ;" +
"          @SetField( EditField[ef];@GetField(EditField[ef]) : @TextToTime( @Explode( RawValue;Separator ) ) ) ) ;" +
"   @Contains(DefaultDataType; \"Integer\" );" +
"      @If( @IsError( @TextToNumber( @Explode( RawValue;Separator ) ) ) ;" +
"          \"\" ;" +
"          @SetField( EditField[ef];@GetField(EditField[ef]) : @TextToNumber( @Explode( RawValue;Separator ) ) ) ) ;" +
"   @SetField( EditField[ef];@GetField(EditField[ef]) : @Explode( RawValue;Separator ) ) )$"+
"Sort Ascending|@Sort(@GetField(EditField[ef]);[Ascending] )$"+
"Sort Descending|@Sort(@GetField(EditField[ef]); [Descending])";"$" );
 
DataTypes:=@Word( DataTypesCombo;"|";1 ); 
DataTypesAction:=@Word( DataTypesCombo;"|";2 );

Setup

First we need to determine the fields that are available, based on the currently selected document. Then get the last updated field based on the form of the currently selected document.
REM {Get a listing of all the fields on the current document};
List:=@Sort( @DocFields );
 
REM {Look for last field modified in Profile Doc};
FieldList:=@Explode( @GetProfileField( ProfileName;EditLastField;@UserName );Arrayseparator;@True ) ;
 
REM {Get the list of forms and field that was updated using Edit Document fields};
FieldListForms:=@Word( FieldList;EditLastSeparator;1 );
FieldListField:=@Word( FieldList;EditLastSeparator;2 );
FieldListLastIndex:=@Member( Form; FieldListForms );
REM {If the FieldListLastIndex is greater than zero then set the last field to the what was in the profile document};
@If( FieldListLastIndex > 0;
 &nbsp @Do( LastField:=FieldListField[ FieldListLastIndex ];
      FieldList:=@ReplaceSubstring( FieldList;FieldList[ FieldListLastIndex ];"" ) );
   LastField:="" );

Field Selection/Add

Display a list of all available fields with the ability to enter new ones. Then set the prompt title that will be used throughout the rest of the code. Select Field
REM {Prompt for which field needs to be updated. Loop until a field is selected or 'Cancel' is selected};
@DoWhile(
   EditField:=@Prompt( [OkCancelEditCombo];PromptTitle;"Select the field you wish to alter:";LastField;@Trim( @Unique(
 List : LastField ) ) );
   EditField="" );
EditFieldPromptTitle:=EditField+" - "+PromptTitle;

Determine Field Type

Using the form of the currently selected document and the field that was just selected to determine the last data type used and the last formula code used. If there was no previous data type try to determine the data type base on the data of the field. If there are any problems the default field type will be text.
REM {This will allow the retrieval of the data type of the field that was last selected. Data is stored like Form+Field%~%DataType.};
FormFieldList:=@Explode( @GetProfileField( ProfileName;EditLastFieldDataType;@UserName );Arrayseparator;@True ) ;
FormFieldListFormField:=@Word( FormFieldList;EditLastSeparator;1 );
FormFieldListDataType:=@Word( FormFieldList;EditLastSeparator;2 );
FormFieldListFormulaCode:=@Word( FormFieldList;EditLastSeparator;3 );
FormFieldListIndex:=@Member( Form+EditField; FormFieldListFormField ); 
@If( FormFieldListIndex > 0;
   @Do( DefaultDataType:=FormFieldListDataType[ FormFieldListIndex ];
      FormFieldListFormulaCode:=FormFieldListFormulaCode[ FormFieldListIndex ];
      FormFieldList:=@ReplaceSubstring( FormFieldList;FormFieldList[ FormFieldListIndex ];"" ) );
   DefaultDataType:="" );
 
REM {If there was no data type used for the field on the form the try to determine the data type};
DefaultDataType:=
   @If( DefaultDataType != "" ;
      DefaultDataType ; 
      @If(
        @IsNumber( @GetField( EditField ) ) ;
            @If( @Count( @GetField( EditField ) ) > 1 ;
               "Integer Multi Value" ;
               "Integer" ) ;
        @IsTime( @GetField( EditField ) ) ;
            @If( @Count( @GetField( EditField ) ) > 1 ;
               "Date Mult iValue" ;
               "Date" ) ;
        @If( @Count( @GetField( EditField ) ) > 1 ;
               "Text Multi Value" ;
               "Text" )
        )
   );
 
REM {If the data type is a type of error then select the data type of text};
DefaultDataType:=@IfError( DefaultDataType;"Text" );

Select Data Type/Set Action

Once the data type is selected determine the posistion in the array of the data type and set the data type action with the action in the same position of the array. Select Data Type
REM {Prompt for which data type you would like the data to be. This needs to be done before value prompt to determine if the Picklist or any prompting needs to be used.}; 
DataType:=@Prompt( [OkCancelList];EditFieldPromptTitle; "Please select the correct data type or action for field: "+EditField+"."; DefaultDataType;DataTypes );
 
REM {The DataTypeAction will contain the formula that will be executed to retrieve the new value};
DataTypeAction:=DataTypesAction[ @Member( DataType;DataTypes ) ];

Get Original Value

When using the formula data type there is no reason to see the selected fields value, because you would reference the field name when writing formula. Since there is no real need of the field value why not show the last formula for that field.
REM {If formula was used on this field then use that instead of the fields value. Format the original value as text because the @Prompt command requires text.};
OriginalValue:=@If( DataType="Formula" & DefaultDataType="Formula" & FormFieldListFormulaCode != "" ;
   FormFieldListFormulaCode ;
   @If( @Contains( DefaultDataType;MultiValue ) ;
      @Implode( @Text( @GetField( EditField ) );rrayseparator );
      @Text( @GetField( EditField ) ) )
   );

Replace & Replace Substring

The idea behind the search and replace is to update all occurances of a string, so why not search over multiple fields. This allows multiple fields to be selected. Additional Fields Then determine what to search for. Replace
REM {Prompt for additional fields and determine the string that they are searching for.};
@If( DataType=@Explode( "Replace Substring;Replace";Arrayseparator ) ;
   @Do( EditField:=@Unique( EditField : @Prompt( [OkCancelListMult];PromptTitle;"Select any addtional fields you wish to alter:";EditField;List ) );
      FromRawValue:=@Prompt( [OkCancelEdit];EditFieldPromptTitle;"Please enter the text (Case sensitive) to search for in: "+@Implode( EditField;", " )+".";"" ) );
   @Do( EditField:=EditField;
      FromRawValue:="" )
   );

Implode/Explode

When exploding multiple characters can be used to separate text. If no separator is entered a semicolon will be used. When imploding the string entered will be used to seperate the values. separator
Separator:=@If( DataType=@Explode( "Implode;Explode";Arrayseparator ) ;
   @Prompt( [OkCancelEdit];PromptTitle;"Enter the "+@If( DataType="Implode";"separator";"separators" )+Arrayseparator;"" );
   Arrayseparator );

Get New Value

Most data types don’t have any promptings like Unique or Sort, the rest the prompt depends on the data type. Enter New Value.GIF
REM {Based on what type of data is being entered different prompts will happen if any at all.}; 
RawValue:=@If( 
   @Contains( DataType;"Name Multi Value" );@PickList( [Name] ); 
   @Contains( DataType;"Name" );@PickList( [Name] : [Single] ); 
   DataType=@Explode( "Remove Field;Unique;Sort Ascending;Sort Descending;Implode;Explode;Proper Case Text;Proper Case Text Multi Value;Lower Case Text;Lower Case Text Multi Value;Upper Case Text;Upper Case Text Multi Value;Password Convert";Arrayseparator );"" ; 
   @Contains( DataType;"Multi Value" );@Prompt( [OkCancelEdit];EditFieldPromptTitle; "Please enter the new desired value for: "+@Implode( EditField;", " )+"."+PormptNewLine+"Seperated
 with;for each value.";OriginalValue ) ;
   @Contains( DataType;"+ Append Values" );@Prompt( [OkCancelEdit];EditFieldPromptTitle; "Please enter values to append: "+@Implode( EditField;", " )+"."+PormptNewLine+"Seperated with;for each value.";"" ) ;
   DataType=@Explode("Replace Substring;Replace";Arrayseparator );@Prompt( [OkCancelEdit];EditFieldPromptTitle;"Please enter the text to repalce with in: "+EditField+".";"" ) ;
   @Prompt( [OkCancelEdit];EditFieldPromptTitle;"Please enter the new desired value for: "+EditField+".";OriginalValue )
   );

Update Profile

Now that the data type has been selected and the new value or forumula has been entered lets set the profile document so it will be available the next time.
REM {Store Field in Profile doc}; 
@SetProfileField( ProfileName;EditLastField;@Unique( @Explode( FieldList : ( Form+EditLastSeparator+EditField[1] );Arrayseparator;@False ) ); @UserName );
 
REM {Store Data Type of Field in Profile doc};
@SetProfileField( ProfileName;EditLastFieldDataType;@Unique( @Explode( FormFieldList : ( Form+EditField[1]+EditLastSeparator+DataType+EditLastSeparator+@If( DataType="Formula";RawValue;FormFieldListFormulaCode ) );Arrayseparator;@False));@UserName );

Find all the documents to be updated

First need to make sure that the current document is one that is selected. Since there is no way to know if it is selected we need to move to the next selected document. The next block of code eleminates the blank value in the list of Note IDs lets set the NoteIDList with the newly selected document. Finally we can navigate through all the selected documents to gather all the documents that will be updated. The last step is to write to the status bar how many documents were found.
REM {If multi docs selected, only process those checked ... an unchecked doc cannot be NavNextSelected};
@Command([NavNextSelected]);
@UpdateFormulaContext;
 
REM {Store all Note IDs before manipulation in case field modifications cause categorized views or sorted columns to reorganize};
NoteIDList:=@Text( @NoteID );
@Command([NavNextSelected]);
@UpdateFormulaContext;
 
REM {Start Looping Selected documents};
@While((@Left(NoteIDList;NoteEntryLength) != (@Text( @NoteID+Arrayseparator))) & ( @Length(NoteIDList) < MaxSearchForSelectedDocs) ;
   NoteIDList:=NoteIDList+Arrayseparator+@Text(@NoteID);
   @Command([NavNextSelected]);
   @UpdateFormulaContext
);
 
NoteIDList:=@Unique( @Explode( NoteIDList;Arrayseparator;@False ) );
@StatusBar( "Found "+@Text( @Elements(NoteIDList))+" documents." );
NotNoteIDList:="";

Update the field

For each document determine if it needs to be updated. Not all selected documents need to be updated becuase it could have moved in the view after it was updated. If there are any errors while updated the field then stop. If the max number of documents that can be navigated to is met then assume that some of the documents can not be found. This eliminates the possibility of an infinite loop.
REM {Loop through selected docs taking each NoteIDList out of the list as it is processed};
DocUpdateCount:=0;
DocNavigationCount:=0;
@While(DocUpdateCount < @Elements(NoteIDList) ;
   NoteIDList:=@Replace(NoteIDList;@NoteID;"") ;
   NotNoteIDList:=NotNoteIDList : @NoteID;
   @For(ef:=1; ef <= @Elements(EditField); ef:=ef+1;
      formulaResult:=@Eval(DataTypeAction);
REMark:=" **REM** The values entered above will be applied to all selected doc. If data conversion doesn't work then don't set field.";
      @If(@IsError(formulaResult);
         @Return(@Prompt([Ok] ;"Error";@Text( FormulaResult)));
      @SetField(EditField[ef];formulaResult)
      )
   );
   @If(DocNavigationCount > MaxUpdatedDocuments ;
      NoteIDList:="";
      @Do(
         DocUpdateCount:=DocUpdateCount+1;
         @Command([NavNextSelected]);
         @UpdateFormulaContext;
REMark:=" **REM** If we haven't processed all docs yet but the current doc is not in the NoteIDList list, keep looping ... if cnt exceeds MaxUpdatedDocuments assume infinite loop and stop ";
         @If(DocUpdateCount < @Elements( NoteIDList) & (!@Member( @NoteID;NoteIDList)) & ( !@Member(@NoteID;NotNoteIDList));
            @While((! @Member(@NoteID;NoteIDList) & DocNavigationCount < MaxUpdatedDocuments);
               @Command([NavNextSelected]);
               @UpdateFormulaContext;
               DocNavigationCount:=DocNavigationCount+1);
         "")
      )
  )
);

Display results

If there are any documents that have not been updated the write the Note ID(s) to the status bar. Also update the status bar with the number of documents that were navigated through and the number of documents updated. That’s it.
@If( @Implode( @Unique( @Explode( NoteIDList;Arrayseparator;@False ) ) ) !="";@StatusBar( "Unable to update the following documents: "+@Implode( @Unique( @Explode( NoteIDList;Arrayseparator;@False ) );", " )+"." ); "" );
@StatusBar( "Navigated through "+@Text( DocUpdateCount+DocNavigationCount )+" documents." );
@StatusBar( "Performed '"+DataType+"' for '"+@Implode( EditField;", " )+"' field"+@If( @Elements( EditField ) > 1; "s ";" " )+"on "+@Text( DocUpdateCount )+" document"+@If( DocUpdateCount > 1;"s";"" )+".")
Download all the code