xGrid And Performance Optimization

Yesterday, I took a closer look at the xGrid custom control, that has recently been posted on OpenNTF by Pablo Solano.
The control uses jqGrid, a jQuery plugin. It lets you display data from a view in a grid. jqGrid has some very nice features. For more details take a look at the demo page.
The download from OpenNTF also contains a set of demo data ( 40.000 person documents ).

I copied the sample application to my server ( Domino 8.5.3 64bit, 16GB RAM, 4 Core AMD ), opened every view in client to create the indexes, signed application and finally opened xContactsSSJS.xsp in Firefox 12.

Next, I took a closer look at the design.

xContactsSSJS.xsp contains a duplicate line of code, which can be deleted
var docs:NotesDocumentCollection = database.search(query);

 

Although the grid was rendered almost immediately in the browser, I wanted to do some time measurement to see how long the retrieval of 40.000 documents would last and if there is some room for optimization.
So I added two lines of code into xJsonContacts.xsp at the beginning and the end of the code that simply calculates the time difference in milliseconds from start to end.

try{
	var start = new Date().getTime();
	var externalContext = facesContext.getExternalContext();
	var writer = facesContext.getResponseWriter();
	var response = externalContext.getResponse();

	// Set content type
	response.setContentType("application/json");
	response.setHeader("Cache-Control", "no-cache");

	// Get all Contacts
	var query = 'Form = "Contact"';

	var docs:NotesDocumentCollection = database.search(query);

	json = "";
	var doc = docs.getFirstDocument()

	while (doc != null) {
		json = json + '{"@unid":"'+ doc.getUniversalID() + '","FirstName":"' + doc.getItemValueString("FirstName") +
		 '","LastName":"' + doc.getItemValueString("FirstName") +  '","State":"' + doc.getItemValueString("State") +
		 '","City":"' + doc.getItemValueString("City") + '"},'		

		// Get next doc and recycle
		tempdoc = docs.getNextDocument();
		doc.recycle();
		doc = tempdoc;
	}

	json  = "[" + @Left(json, @Length(json) - 1) + "]";	

	writer.write(json);
	writer.endDocument();
	var elapsed = new Date().getTime() - start;
	print("xContactsSSJS.xsp ->" + elapsed +" ms");

} catch(e){
	_dump(e);
}

I then opened the page 10 times in the browser. here are the results

The code builds a JSON string at runtime. So, for every element, it has to access a document from the document collection and get the item values one after the other from the document.

I changed the code; first of all, I copied the Contacts view an deleted all columns except one. I put the following code into the column formula.

_fld:="FirstName":"LastName":"State":"City";

"{\"@unid\":\""
+@Text(@DocumentUniqueID)+"\","
+ @Implode (
@Transform (
_fld; "_fn" ; "\"" + _fn + "\":\"" + @Text ( @GetField ( _fn) ) + "\"" ) ; "," ) + "},"

The formula computes a JSON string for every document in the view. This avoids the need to build the string at runtime in javascript.

The code for the XAgent looks like this:

try{
	var start = new Date().getTime();
	var externalContext = facesContext.getExternalContext();
	var writer = facesContext.getResponseWriter();
	var response = externalContext.getResponse();

	// Set content type
	response.setContentType("application/json");
	response.setHeader("Cache-Control", "no-cache");
	json = ""
	  var v:NotesView = database.getView("ContactsSingleCol");
	  //do not do AutoUpdates
	  v.AutoUpdate = false;
	  var nav:NotesViewNavigator = v.createViewNav();
	  nav.setEntryOptions(
	  NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
	  //enable cache for max buffering
	  nav.BufferMaxEntries = 400
	  var entry:NotesViewEntry = nav.getFirst();

	  while (entry != null) {
	    json=json + entry.getColumnValues().elementAt(0).toString();
	    var tmpentry:NotesViewEntry = nav.getNext(entry);
	    entry.recycle();
	    entry = tmpentry;
	  }

  	writer.write('[' + @Left(json, @Length(json) - 1) + ']');
	writer.endDocument();
	var elapsed = new Date().getTime() - start;
print("xContactsSSJSViewNav.xsp ->" + elapsed +" ms");

} catch(e){
	_dump(e);
}

As you can see, the code uses a NotesViewNavigator to iterate thru the view entries and concat the values from the first column of the view containing the pre-build JSON string.

Once again, I opened the page in the browser; here are the results from my simple time measurement.

Conclusion: Building the JSON in advance and using a NotesViewNavigator speeds up the loading of the data.

In the above code, the new value is added to the existing JSON String using the “+” sign. Using the ‘+’ operator for concatenation isn’t bad per se though.

It’s very readable and it doesn’t necessarily affect performance. Each time you append something via ‘+’ a new String is created, the old stuff is copied, the new stuff is appended, and the old String is thrown away. The bigger the String gets the longer it takes – there is more to copy and more garbage is produced.

An alternative way to concat strings is using a java.lang.StringBuilder. As you might know, you can use Java in your server-side javascript.

xJsonContactsViewNavSb.xsp shows, how to use a stringbuffer

try{
	var start = new Date().getTime();
	var externalContext = facesContext.getExternalContext();
	var writer = facesContext.getResponseWriter();
	var response = externalContext.getResponse();

	// Set content type
	response.setContentType("application/json");
	response.setHeader("Cache-Control", "no-cache");

	  var json:java.lang.StringBuilder = new java.lang.StringBuilder();
	  var v:NotesView = database.getView("ContactsSingleCol");
	  //do not do AutoUpdates
	  v.AutoUpdate = false;
	  var nav:NotesViewNavigator = v.createViewNav();
	  nav.setEntryOptions(
	  NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
	  //enable cache for max buffering
	  nav.BufferMaxEntries = 400
	  var entry:NotesViewEntry = nav.getFirst();

	  while (entry != null) {
	    json.append( entry.getColumnValues().elementAt(0).toString());
	    var tmpentry:NotesViewEntry = nav.getNext(entry);
	    entry.recycle();
	    entry = tmpentry;
	  }

  writer.write('[' + @Left(json.toString(), @Length(json.toString()) - 1) + ']');
	writer.endDocument();
	var elapsed = new Date().getTime() - start;
print("xContactsSSJSViewNavSb.xsp ->" + elapsed +" ms");

} catch(e){
	_dump(e);
}

Opening the page in the browser shows the following results

Impressive, isn’t it?

As a fazit, even in XPages programming there is room for views and @formulas.

Uses these elements to pre-calculate values and move away this work from the runtime. Use a NotesViewNavigator to access data from the view. Read this article to find out more about NotesViewNavigator.
Conclusion: If you have to concat a large number of strings, use a java.lang.StringBuilder instead of the “+” operator.

Having done all these steps to optimize performance, it is only a small step to use Java instead of javascript to do the heavy lifting.

Here is the code from xJsonContactsJava.xsp

try{
	var start = new Date().getTime();
	var externalContext = facesContext.getExternalContext();
	var writer = facesContext.getResponseWriter();
	var response = externalContext.getResponse();

	// Set content type
	response.setContentType("application/json");
	response.setHeader("Cache-Control", "no-cache");
	var out:de.eknori.ViewColumn = new de.eknori.ViewColumn();
	writer.write(out.getViewColumnValueJSON("ContactsSingleCol",0));
	writer.endDocument();
	var elapsed = new Date().getTime() - start;
	print("xContactsSSJSJava.xsp ->" + elapsed +" ms");

} catch(e){
	_dump(e);
}

The getViewColumnValueJSON() method is located in the de.eknori.ViewColumn class.

package de.eknori;

import static com.ibm.xsp.extlib.util.ExtLibUtil.getCurrentDatabase;
import lotus.domino.NotesException;
import lotus.domino.View;
import lotus.domino.ViewEntry;
import lotus.domino.ViewNavigator;

public class ViewColumn {
	private static final String MSG_STRING_ERROR = "ERROR: ";
	private static final String MSG_STRING_NOT_FOUND = " not found";

	public ViewColumn() {
	}

	public String getViewColumnValueJSON(String viewname, int pos) {
		ViewNavigator nav = null;
		StringBuilder json = new StringBuilder();
		json.append('[');
		String strValue = "";
		try {
			View view = getCurrentDatabase().getView(viewname);
			if (null != view) {
				view.setAutoUpdate(false);
				nav = view.createViewNav();
				nav.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
				nav.setBufferMaxEntries(400);
				ViewEntry entry = nav.getFirst();

				while (entry != null) {
					json.append(entry.getColumnValues().elementAt(pos)
							.toString());
					ViewEntry tmpentry = nav.getNext(entry);
					entry.recycle();
					entry = tmpentry;
				}
				strValue = json.toString();
				strValue = strValue.substring(0, strValue.lastIndexOf(","))
						+ "]";
				view.setAutoUpdate(true);
			} else {
				System.out.println(MSG_STRING_ERROR + viewname
						+ MSG_STRING_NOT_FOUND);
			}
		} catch (NotesException e) {
			System.out.println(MSG_STRING_ERROR);
			strValue = "[{}]";
		}
		return strValue;
	}
}

As you can see from the entries in the screenshot, there is another gain in performance when using Java.

If your application has a large amount of data, I strongly advice to use Java instead of JavaScript.

I’m doing Java for no longer than 6 month by now. If you are familiar with LotusScript, you will be able to learn the basic java stuff real quick. There is no excuse to not start learning Java right now.

You can download the application including my modifications here.  (design only!)


xe:switchFacet – Dynamically switch content in XPages

Several roads are leading to Rome and so it is in XPages. There is always a different solution to solve a challenge.
Assume, you are using the Application Layout Control from the eXtension Library. The control offers an easy way to build a standard GUI in a few minutes.
It can have tabs and links and other things that are needed in the application to display content and switch between the different sections of an application.
The sample application, that comes with the eXtension Library shows one way to switch the content when a user clicks title bar tabs.

Here is another solution to achieve the goal. The application uses one XPage, a set of custom controls and a Variable Resolver. The variable resolver is responsible to highlight the selected value and to set the default tab. Switching the content of the left facet in the Application Layout Control is done by evaluating the value submitted via a scoped variable by a xe:switchFacet control.

Here is a picture of what the output in the browser should look like.

And here is the code for the different design elements.

home.xsp

The interesting part is in the beforeRenderResponse event of the page. setDefaultTab is a variable that is evaluated by the variable resolver. If viewScope.selectedTab does nor contain a value, a default value is returned and the according tab gets the focus.

ccLayout

ccLayout also uses the variable resolver. isTab1Selected and isTab2Selected return true or false according to value that is submitted, when the user clicks on a tab. In the onItemClick event, viewScope.selectedTab is set to the value that is defined in the submitValue= of a tab

ccSwitchFacet

The selectedFacet parameter in the xe:switchFacet control consumes the value in viewScope.selectedTab and then displays the content of the facet, tah matches the value.

ccNavTab1 / ccNavTab2

Variable Resolver

And finally, here is the code for the variable resolver. The sceleton for the code has been taken from OpenNTF XSnippets. Paul Withers has submitted the code.

package com.isatweb.jsf.core;

import static com.ibm.xsp.extlib.util.ExtLibUtil.getViewScope;

import java.util.Map;

import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.VariableResolver;

public class VarResolver extends VariableResolver {

private final VariableResolver delegate;
private final String scopeVarSelectedTab = "selectedTab";

public VarResolver(VariableResolver resolver) {
delegate = resolver;
}

@Override
public Object resolveVariable(FacesContext context, String name)
throws EvaluationException {

Map<String, Object> viewScope = getViewScope();
Object sel = viewScope.get(scopeVarSelectedTab);

if ("isTab1Selected".equals(name)) {
if (sel == null) {
sel = false;
}
if (sel.toString().equals("tab1")) {
return true;
}
}

if ("isTab2Selected".equals(name)) {
if (sel == null) {
sel = false;
}
if (sel.toString().equals("tab2")) {
return true;
}
}

// sets the DEFAULT tab
if ("setDefaultTab".equals(name)) {
if (sel == null) {
viewScope.put(scopeVarSelectedTab,"tab1");
}

}

return delegate.resolveVariable(context, name);

}
}

You can download the complete application from here


org.apache.commons.collections – MultiValueMap

Maps, Treemaps and whatever have you, are a great way to store data for easy access the data by a given key. The disadvantage with this objects is that the key has to be unique. This means that you cannot store different values associated with one key.

I needed a solution to store data in a map and each key should be used for multiple values

key = human, value = Batman
key = human, value = Captain America
key = alien, value = Superman

Searching the web, I found org.apache.commons.collections

A MultiMap is similar to a Map, but which may associate multiple values with a single key. If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.

This makes it very easy to filter a map by a given key.

package de.eknori.test;

import org.apache.commons.collections.map.MultiValueMap;

public class MultiMap {

	public static void main(String[] args) {

		MultiValueMap superheroes = new MultiValueMap();
		String key = "human";
		superheroes.put("mutant", "Wolverine");
		superheroes.put("mutant", "Beast");
		superheroes.put("alien", "Superman");
		superheroes.put("human", "Batman");
		superheroes.put("human", "Captain America");

		System.out.println(key + " superheroes: " + superheroes.get(key));
		System.out.println("There are " + superheroes.size()
				+ " different kind of superheroes: " + superheroes.keySet());

		System.out.println("All superheroes: " + superheroes.values());
	}
}

Running the code will show the following on the console:

human superheroes: [Batman, Captain America]
There are 3 different kind of superheroes: [alien, mutant, human]
All superheroes: [Superman, Wolverine, Beast, Batman, Captain America]


Lotus Domino XPages Praxis-Seminar (3 Tage) – Hannover

2Consultants bietet wieder eines ihrer fundierten XPages Seminare an.

Das XPages Praxis Seminar findet statt in Hannover vom 22. bis 24. Juni mit neuem und eigenem Konzept inklusive deutschsprachiger Unterlagen (250 Seiten in Farbe).

Als einen weiteren Vorteil sehe ich die Durchführungsgarantie. Im Seminarpreis enthalten ist auch die Nutzung des “fliegenden Klassenzimmers”.

Wer sich also im Raum Hannover mit dem Gedanken trägt, sich nun endlich mit XPages zu beschäftigen, der sollte jetzt zugreifen und noch heute die Anmeldung ausfüllen.

 

 

 


SSJS Extension – New Function: @GetNextBusinessDayExt()

I have added a new @Function to my SSJS project on OpenNTF.

Syntax

@GetNextBusinessDayExt( [offset] ; [baseDate] ; [excludedDaysOfWeek] ; [excludedDates] )

Parameters

offset:
Number of non-excluded days from the baseDate that the result date will be

basedate:
Date to start counting offset from. If specify “null” defaults to today

excludedDaysOfWeek:
Numer or number list. Optional. Days of the week not counted as business days, where 1 is Sunday and 7 is Saturday.
DEFAULT: 1,7 ( Sunday, Saturday )

excludedDates:
String of date strings (formatted as <CODE>SimpleDateFormat.getDateInstance().format(</CODE>)) which represent specific dates which should be excluded from the counting of days, such as holidays

Return value:
The earliest date which is offset days after the baseDate. In  counting towards this result date, any excludedDaysOfWeek and any excludedDates are excluded from counting towards the offset


Joda to the rescue

Today I had to compare to dates to find out, if the are on the same date. Both dates had a DateTime format. So my first thought was to just strip the date part. I looked at the java.util.Date class and found that most of the methods are deprecated.

Sure, you can pass the java.util.Date objects to a java.util.Calendar and sethours, minutes and seconds to zero.

But I was looking for a smarter way to accomplish the goal. Joda-Time provides a quality replacement for the Java date and time classes. Joda-Time has been created to radically change date and time handling in Java. The JDK classes Date and Calendar are very badly designed, have had numerous bugs and have odd performance effects.

Using Joda, comparison of dates is easy as pie. The toDateMidnight() method for example sets the time part to zero.

Now you can do the compare with a simple date1.compareTo(date2) .

 


Access UserBean from Java

I use the userBean that comes with the extension Library in SSJS very often. It is a convenient way to access various properties of the current user.

Today I wanted to use the UserBean in Java. But I could not figure out, how to do. After some help from Stephan Wissel and a few minutes of try and error, I ended up with the following:

package de.eknori.jsf.core;
import com.ibm.xsp.extlib.beans.*;

public class User {

public static Object getUser() {
return UserBean.get().getField( "effectiveUserName" );
}
}

A UserBean.get(9 will return all fields as shown in the picture below

The getField(String) method lets you access a single value from the bean.

 


SSJS – @CancelPartialRefresh

Inspired by the great work of Sven Hasselbach, who blogged about his XSnippet to cancel a partialrefresh, I took the JavaScript code and injected it into my SSJS Extension Project. At the moment, the code is undergoing some testing, but it seems that it is stable and ready for release.

Using a simple @CancelPartialRefresh() in your SSJS code, you can cancel the execution of code during a partial refresh at any point of your existing SSJS code.


[DanNotes] – Presentation SlideDeck

DanNotes is just around the corner. This is the second time for me to present at this conference.

Here is the slidedeck from my DanNotes presentation “XPages – beyond the Basics“. The session is an extended version of the content, I presented at BLUG 2012.

At DanNotes I have a 2h timeslot.This gives alot more room for demoes and even more topics that can be covered.

Looking forward to the conference and meeting with the other speakers and the attendees, of course.


Small Cause – Big Effect

Got a new laptop this week and had to re-install some software. After installing BIRT and a local Domino, I configured BIRT to acces a database on the local server to test DomSQL.

I spent the whole evening yesterday and could not figure out, what was wrong with my configuration. The connection to the server could be established, but no table from the Notes application was accessible.

A few minutes ago, I opened the configuration again. How dumb ( blind ) am I ? The cause was a missing slash … *doh*


[XSnippet:] Refresh applicationScope variables with individual timeout

applicationScope is a great way to store data, that is available without constantly reading the values from views or config docs. But from time to time even config data changes and when the values are stored in applicationScope, the changes are not pushed to the application in a reasonable timeframe.
The code snippet lets you update applicationScope variables on an individual schedule.

The sample code update applicationScope.test every 60 seconds.

var test = new de.eknori.Tools();
if (test.refreshCache("test", 60)) {
var currentTime = @Now();
applicationScope.test = currentTime;
return "New value: " + currentTime ;
} else {
return "Cached value: " +applicationScope.test
}

The java code uses import static com.ibm.xsp.extlib.util.ExtLibUtil.getApplicationScope;
So you have to have the extension Library in place. Otherwise you have to write some lines of Java to access the applicationScope w/o the extension Library.

Set private boolean debugMode = false to switch off debugging.


[Review] – IBM Lotus Notes and Domino 8.5.3 Upgrader’s Guide

An new release, a new upgrader’s guide. Due to their work at IBM, the authors Tim Speed, Scott O’Keefe and Barry Rosen have a deep knowledge of Lotus Notes and Domino.

The IBM Lotus Notes and Domino 8.5.3 Upgrader’s Guide begins with an overview of the SOA characteristics of Lotus Notes and moves on to the features and changes in Lotus Notes Client 8.5.3, before providing an overview of Lotus Symphony productivity tools.

The book is written for Lotus Notes power users ( whatever this means. So far, I do not know anybody, I would call a power user ) administrators and developers.

I ( as a developer ) think that 60 pages, that cover the new and enhanced features in Domino Designer does not justify a recommendation to developers.
Experienced administrators will get no real information that goes beyond the information  contained in the release notes for a new release of Lotus Notes and Domino.

My favorite content is hidden in chapter 7 / page 207 ff. You will find a small script to export the reports from Domino Configuration Tuner.

I recommend this book to managers and/or decision makers.

It can be leveraged by these people to gain a high-level understanding of the new features and capabilities offered.


TabContainer and RichText Control interaction

During the last week we were bitten in the leg by an issue with xp:inputRichText in a xe:djTabPane. When opening a new tabPane, the RichText control was not rendered and displyed as a multiline input text control. In Firebug you could see the following output in the error panel.

At least, we were able to find out, what causes the issue, and we were able to reproduce the behaviour in sample application that comes with the extension Library.

Do the following steps to reproduce:

Open core_DynamicTabs (XPage)

Add a xp:inputRichText somewhere in the table that is contained in the xe:djTabPane section.

When you now open the page in the browser and click on a name in the view, you should something similar to this:

So everything is OK so far.

Now change/ add the partialRefresh property of the xe:djTabPane and give it a value of true.

When you now refresh your browser, you will see this

We found other controls that are alse affected. As a result, a complete actionbar in our project refused to function and worked again as expected as soon as we removed the partialRefresh=”true” from the xe:djTabPane.

Have not tested with oter versions of the extension Library, but we saw this behaviour with the latest release as well as with Upgrade Pack 1.

 


Using a RamDisk with Notes Client

From time to time I have some issues with my Notes Client. In almost every case these issues occur when the CACHE.NDK size is at 30MB.

Bringing the Notes Client down, deleting cache.ndk and let the client create a new file at startup solves the issues.

There is already an entry on IdeaJam to delete the cache.ndk on client startup. (http://ideajam.net/ideajam/p/ij.nsf/0/D599DAE62FA43BBB86257723005994BC). As there is no such option in the client by now, I tried to find a way other than deleting the file manually.

Putting a cmd file into the Windows Startup folder is one option. But I wanted to go one step further. Accessing the cache.ndk does also mean I/O operations on the physical harddrive. OK, I could use SSD, but I do not have an SSD drive in my current environment.

So I decided to give a RAMDISK a try. I found a good one at http://www.ltr-data.se/opencode.html/#ImDisk.

he installer installs he driver as a service, so it can be started at Windows startup. In addition to get the RAMDISK to work you have to create a disk and format it every time Windows starts.

I created a ramdisk.cmd file in my Windows directory and created a shortcut to this file in the Startup folder for all users.

Here is the content:

imdisk -a -t vm -s 300m -m n:
format n: /V:RAMDISK /FS:FAT /Q /Y

The first line creates the disk in RAM ( n:\)  and the second line formats the disk.

In addition, I changed my notes.ini file and added

CACHE=n:\cache.ndk

The next time you start your client, CACHE.NDK is created in the RAMDISK. When you shutdown your computer, the RAMDISK is removed and so a new file is created on every client start.

I will now play with some other notes.ini variables that normally perform disk operations to see, what the side effects are.


FollowUp: Paul Withers: Extending Themes – Custom Pager Labels

Yesterday, Paul Withers published an interisting article on how to customize pager labels using themes. Themes are indeed very powerful. I would like to show you a more advanced example to customize the pager labels according to the users language.

At is@web we are developing an application that is fully multi language enabled. This means, that all labels, messages and whatsoever comes from resource files. Using resource files, you can translate your application into any language.

All you have to do is to add the resource-bundle to your XPage

I use a static value for src= in this sample; our application calculates the src= value dynamically. The user can choose any available language at runtime.

We also have a managed bean that provides a method to access the resource bundle. I have blogged about this here.

In your theme, you can now calculate the value property of the control using this method.

I will show stuff like this in my #BLUG session: “XPages – Beyond The Basics”


[Guest Post] IBM Should Support IBM Connections, IBM Sametime and Lotus Notes Traveler on IBM i

Why Should these IBM Collaboration Solutions (ICS) products run on IBM i?

  • IBM i on Power Systems and it’s predecessors have literally hundreds of thousands of installations worldwide.
  • IBM i customers want to leverage existing investments in hardware, software, personnel and training.
  • Running ICS servers on other platforms complicates IBM i customer environments. Many customers have a single IBM i server running their entire business.
  • Better performance – All server to server communications is via the Power Systems high speed bus.
  • Better security – IBM i is impervious to viruses. Period.
  • Better uptime – IBM i has a higher uptime % than any other operating system
  • IBM i is IBM’s most widely deployed operating system, over AIX, Power Linux and zOS.
  • Fewer points of failure – One system to manage, administer, maintain, backup and recover.

200 IBM i customers and IBM Business Partners representing 520,000 potential licenses have already expressed interest in these products only if they were supported on IBM i

Go to this website and tell IBM that you want support for IBM Connections, IBM Sametime and Lotus Notes Traveler on IBM i.

This post was brought to you by my fellow yellowbleeder Steve Pitcher


My BLUG schedule

The next BLUG conference is just around the corner. From March 22-23, 2012 the yellow bleeding community will gather together in Antwerp, Belgium.

I’m proud to be on the speaker list and I will present on “XPages – Beyond the Basics“. The session will cover themes, the eXtension Library, JAVA / JAR design elements and also access to relational databases using JAVA and a few lines of JavaScript.

I will arrive on wednesday together with my new team member Sarah Steffen and my good friend Werner Motzet. After a few minutes of discussion, Werner decided to fly in from Nürnberg to Düsseldorf and we then will drive to Antwerp by car.

On Friday we possibly have to leave before the CGS to get Werner back to Düsseldorf airport in time.

If you still not have registered for BLUG, do it now. It’s FREE!! Look at the agenda and see what you are missing …

By the way, here is my schedule for the conference.

 


Access Resource Bundle in XPages from JAVA

Today, I tried to figure out how to access a resource bundle and return a value for a given key. I have the extensionLibrary installed and so I’m using some of the methods that are provided by the ExtLibUtils class.

To make the class available in your application, add the following line to your JAVA code.

import static com.ibm.xsp.extlib.util.ExtLibUtil.*;

The next lines of code gives you access to a global variable. When you make use of a resource bundle, a global variable is returned and you can access the bundle via this variable.

public Object getGlobalObject(String obj) throws Exception {
   return resolveVariable(FacesContext.getCurrentInstance(),obj);
}

And here is the funktion that extracts the value from the key/value pair in the resource:

public String getLangString(String bundle,String key) throws Exception {
  try{
     ResourceBundle rb = (ResourceBundle)this.getGlobalObject(bundle);
     return rb.getString(key);
  }catch (Exception e) {
     e.printStackTrace();
     return "##NOT FOUND##";
  }
}

You now can use the method to return a value for a given key from a specific resoure bundle

getLangString('myButtonLabels','CANCEL');

If there is an alternative way, pls. let me know. I’m sure there is. …