RAD Table walker

For a project I received some very unclear description for a change. After a conversation I came up with the suggestion to rebuild the UI and present a Bootstrap table with add and delete buttons. Something what developers would call a table walker.

So how did I do this?

First I had to extend the Model object of my MVC model. The table repeats rows with persons details so I added:

The Person class is simplified as followed:

On my model object I also added getters and setters and and an add method:

Then I had to build the table with the following important elements:

  • a repeat control to represent my arraylist of Person objects
  • input fields bounded to the Person name and email
  • a button for each row to remove the Person from the arraylist
  • a button to display a new blank row to register a new Person

The code is not that long or complicated. I highlighted the important parts with bleeding yellow:

In the next phase I decided to replace the input field the name field with namespickers (by first name or by last name) wrapped as Bootstrap field add-ons. Here is the rough code:

This all resulted in a nice BS table with add / remove buttons:

Now I am just waiting for customer approval. That is mostly the longest part in Rapid Application Design.

Fiddling with jax-rs and getting frustrated

Today was probably not the best day for development. After getting the Jakarta EE project to work on the development server by a miraculous second signing of the plugins in the update site NSF and restart of HTTP on Domino (v10) my next step was to actually getting to get some code running.

Curious as I am I found some code samples here , here and here but after trying some of the samples in my NSF I keep on running on errors:

[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM: ERROR i18n RESTEASY002025: Unknown exception while executing GET /sample/counter
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001318: Cannot resolve an ambiguous dependency between: 
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default],
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default]
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.weld.manager.BeanManagerImpl.resolve(BeanManagerImpl.java:1164)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.cdi.CdiConstructorInjector.construct(CdiConstructorInjector.java:66)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.cdi.CdiConstructorInjector.construct(CdiConstructorInjector.java:73)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.createResource(POJOResourceFactory.java:67)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:309)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:439)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher$$Lambda$95.0000000046F8EF20.run(Unknown Source)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher$$Lambda$96.0000000046F8F8D0.get(Unknown Source)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:355)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.openntf.xsp.jaxrs.impl.FacesJAXRSServletContainer.service(FacesJAXRSServletContainer.java:96)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:600)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:1352)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:877)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:820)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:589)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1336)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:662)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:482)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:357)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:313)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM: org.jboss.resteasy.spi.UnhandledException: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001318: Cannot resolve an ambiguous dependency between: 
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default],
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default]
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:257)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:193)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:455)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher$$Lambda$95.0000000046F8EF20.run(Unknown Source)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher$$Lambda$96.0000000046F8F8D0.get(Unknown Source)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:355)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.openntf.xsp.jaxrs.impl.FacesJAXRSServletContainer.service(FacesJAXRSServletContainer.java:96)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:600)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:1352)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:877)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:820)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:589)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1336)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:662)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:482)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:357)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:313)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM: Caused by: 
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001318: Cannot resolve an ambiguous dependency between: 
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default],
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:   - Managed Bean [class servlet.Sample] with qualifiers [@Any @Default]
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.weld.manager.BeanManagerImpl.resolve(BeanManagerImpl.java:1164)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.cdi.CdiConstructorInjector.construct(CdiConstructorInjector.java:66)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.cdi.CdiConstructorInjector.construct(CdiConstructorInjector.java:73)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.createResource(POJOResourceFactory.java:67)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:309)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:439)
[14EC:0011-0484] 2020-02-12 21:24:57   HTTP JVM:  ... 23 more

Here is the code that I am running:

Getting the Select2-to-Tree working in XPages

In a modernization project (increase browser compatibility) I needed a more simple solution for a custom value picker. The less design elements the merrier 🙂

Another highly desired feature was to have a search function within the picker because there are a lot of values to choose from.

So my initial though was to present the options in a listbox and wrap the select2 plugin around it. Well it turned out the customer found the categorization of options a need to have, including collapse and expand options (who has not grown up with twisties?).

Next attempt was to implement the Select2-to-tree plugin since it presents a hierarchy of links in a select2 object

Looks familiar like a Notes view right? Luckily my Notes view only got one level of categorization and my skills with transforming Notes view data into other objects came in handy. So in a short time I have my data providing REST service setup .

Here is the initiation script for the plugin:

Then I tried to send the selected value to the server to check the value but I was not able to. The XPage did not send a message but the page just frooze.

So to avoid to spend a lot of time on finding and fixing the bug I decided to run the alternative route and use SELECT element instead of the xp:listBox and write the value from csjs to the server via a RPC service (hence the change listener)

<select
	id="lbTest1"
	multiple="true"
	class="form-control">
</select>

The RPC service has nothing special, just setting a viewScope variable:

So that is basically it. With a minimum of design elements / coding I got my Notes view data in a treeview presented in a select2 component:

One item to be removed from my back-log. Happy development 🙂

Data for Bootstrap Treeview from a Notes View?

In a modernization project (increase of browser compatibility) I needed to find a solution for a list of links, categorized and sorted like a Notes View.

Since I already use Bootstrap as CSS framework I decided to check the following Bootstrap Treeview project: https://github.com/jonmiles/bootstrap-treeview .  I was satisfied with the following example(s):

treeview_ex.png

The initialisation is pretty simple:

snipp01

Now I just needed to set up a REST service to provide me the data. The service is setup on an XPage and bounded to a Java class:

snipp02

The Java class I will reuse for providing multiple data streams so it detects the different method parameters:

snipp03

As mentioned the data for the list comes from a Notes View. As you all know views entries have different characteristics which I have to bear in mind:

  • Documents can land anywhere in the view since it is categorized on a text field where the multiple levels can set individually eg Europe\\Sweden\\Stockholm or Afrika\\Johannesburg.
  • The view also holds links for other menus organized under a category so I decided to us a viewnavigator and start collecting data from a specific category.
  • For each entry in the view I have to check what type it is: category, document or total. The last one is not of my interest so I have to skip if that option occurs.
  • The columnindentlevel tells me all about where I am in the view in comparison with the columnindentlevel of the previous entry. Here are the scenarios:

// case 1: current indent level < columnindentlevel
// -> new category, propobably start situation
// case 2: current indent level = column indentlevel
// -> new category (sibling) but close the previous
// one first (just one level)
// case 3: current indent level > column indent
// level -> new category but closes the previous
// one(s) first. how many depends on difference curr
// and column level

Ofcourse categories behave different that documents.

So here is the code:

serv01.PNG
serv02
serv03.PNG

Some notes:

  • I have set the header of the REST service to text/html and the plugin needs a JavaScript object. Therefor capture my data response in an eval() method.
  • A target attribute is not provided by the plugin, so I add one myself. Categories have the # as href so based on that info I include a target attribute or not. I do this via a function:
snipp04
  • If the treeview is collapsed there is no anchor element for underlying list-items so you cannot add the target for all links.
  • Also when collapsing and expanding a category the added target attributes are gone. So for opening of every category you need to re-apply the href attribute for underlying anchors.
  • A category can be opened via a ‘twistie’ image or the text link so we need to register an action on these onclick events
snipp05
snipp06

The result is a nice looking ‘Notes View data-driven’ treeview with Bootstrap styling:

snipp07

Happy coding 🙂

PS. I noticed I have a lot of unused local variables in my code, you may clean that up 🙂

How do you organize your workspace?

Notes users differ in their tasks so my usage of the Notes workspace as a developer is completely different from a user that mainly works with documents within an application.

As always “With great power there must also come — great responsibility” so the Notes client lets us completely free how to setup our workflow via the workspace.

My activities focus mainly on adding code, distribute these changes into several environments (from DEV to PRODUCTION) and also store this code in templates.

If you have similar activities, how do you organize your workspace?

Back with CWPPR0033E error

So after my previous post I uninstalled Notes 9, installed Notes 10 with FP2 and installed the desired plugins. All installations worked like a charm.

But now after a couple of days working with DDE I wanted to install another plugin and BANG! I got the CWPPR0033E  message again.

install_error

Anyone have a suggestion how to install a plugin other than the File – Application – Install process into DDE ?!?

Workaround for updating the styleClass property of a button when using BS theme

In my application I am using the Bootstrap theme that comes with the extension library. In the application I have a button with which I want to enable/disable the appearance of the debugtoolbar plugin for the end-user so it will be easier to handle incident reports when they should occur.

So in my application layout control I added the toolbar and compute the appearance:

debuglayout

Now somewhere in my application I have in a dialog a button to show/hide the debug toolbar:

debugBtn In the browser it looks as followed:

debugDlg

At first when I pressed the button the styleClass would update ONLY when I totally refreshed the page which was undesired.

The trick here is to disable the theme for the button:

debugTheme

I then have to surround the button with additional HTML span element:

<span class=”btn-group”><xp:button…/></span>

As a result the button changes styleClass correctly:

debugSwinging.JPG

Want to know more about XPages dev item? Just drop a question below…

Debugging utility in XPages

Time to re-open an previous XPages project and add some new functionalities for the users. When I did this today I noticed some programming that I never wrote about before, so I do this now 🙂

In an application I wanted to monitor individual classes and write the findings to the console or OpenLog. All managed from a web UI 🙂

The printing method in this app is pretty simple:

public void printToConsole(String msg){
String callerClassName = getCallerCallerClassName();
if (debugClasses.containsKey(callerClassName)){
if (false != debugClasses.get(callerClassName)){
if (true == this.openLog){
OpenLogUtil.logEvent(new Throwable(), msg, Level.INFO, null);
}
else{
System.out.println(msg);
}
}
}
}

The getCallerClass method identifies which class is calling this method:

public static String getCallerCallerClassName() {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
String callerClassName = null;
for (int i=1; i<stElements.length; i++) {
StackTraceElement ste = stElements[i];
if (!ste.getClassName().equals(KDebug.class.getName())&& ste.getClassName().indexOf(“java.lang.Thread”)!=0) {
if (callerClassName==null) {
callerClassName = ste.getClassName();
} else if (!callerClassName.equals(ste.getClassName())) {
return ste.getClassName();
}
}
}
return null;
}

Here is the KDebug class :

package com.wordpress.kwintessential.myproject.utils;

public class KDebug {
public static String getCallerClassName() {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
for (int i=1; i<stElements.length; i++) {
StackTraceElement ste = stElements[i];
if (!ste.getClassName().equals(KDebug.class.getName()) && ste.getClassName().indexOf(“java.lang.Thread”)!=0) {
return ste.getClassName();
}
}
return null;
}
}

debugClasses is declared as followed:

private HashMap<String,Boolean> debugClasses = new HashMap<String,Boolean>();

And it is set when I initiate my utility class that contains the printing method:

public void setDebugClasses(){

try {
Properties debugs = this.getDebugProperties();
Enumeration<String> enums = (Enumeration<String>) debugs.propertyNames();

while (enums.hasMoreElements()) {
Boolean boolean1 = false;
String key = enums.nextElement();
String value = debugs.getProperty(key);
boolean1 = Boolean.valueOf(value);
debugClasses.put(key, boolean1);
}
} catch (IOException e) {
OpenLogUtil.logError(e);
}

}

method getDebugProperties reads a properties file that is stored in the WebContent directory:

monitor

public Properties getDebugProperties() throws IOException {
InputStream input = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(“monitor.properties”);
Properties debugs = new Properties();
debugs.load(input);
return debugs;
}

Note that I have to place the file in the WebContent folder and I could not place it as a Resource design element.

The monitor.properties file contains key values pairs e.g.:

com.wordpress.kwintessential.myproject.dao.DecisionDominoDAO=true
com.wordpress.kwintessential.myproject.dao.VisitorDominoDAO=false

So with this code above I can:

  • check which class is calling the print method
  • check if this class should be monitored (read: print data to the console or OpenLog)

So all great! But in my case I wanted to prevent that the design had to be opened and rebuild every time the monitor settings should be altered.

So in this project I created a web UI (XPage) to read and change the monitor.properties file:

<?xml version=”1.0″ encoding=”UTF-8″?>
<xp:view xmlns:xp=”http://www.ibm.com/xsp/core”&gt;

<xp:this.beforePageLoad><![CDATA[#{javascript:var file = param.get(“filename”)
if (null == file || file == “”){
ctx = facesContext.getExternalContext();
ctx.redirect(facesContext.getExternalContext().getRequest().getRequestURL() + “?filename=monitor.properties”);
}}]]></xp:this.beforePageLoad>
<xp:panel styleClass=”container”>

<h1>Debug <small> Monitor Java classes</small></h1>

FileName:
<xp:inputText
id=”inputTextFileName”
value=”#{fileBean.fileName}”
defaultValue=”#{param.filename}”
disabled=”true” />
<xp:br />
FileData:

<xp:inputTextarea
id=”inputTextarea1″
value=”#{fileBean.fileData}”
rows=”20″
cols=”80″ />
<xp:br />
<xp:panel styleClass=”btnBar”>
<xp:button value=”Load” id=”buttonLoad”
styleClass=”btn-primary”>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”complete” action=”#{fileBean.loadData}”>
</xp:eventHandler>
</xp:button>
<xp:button value=”Save” id=”buttonSave”>
<xp:eventHandler event=”onclick” submit=”true”
refreshMode=”complete” action=”#{fileBean.saveData}”>
</xp:eventHandler>
</xp:button>
</xp:panel>
</xp:panel>
</xp:view>

Here is how it looks in a browser:

web ui initial.JPG

When I press the Load button the content of the monitor.properties file get’s loaded:

web ui loaded

And of course when I press the Save button the monitor.properties file get’s updated 🙂

Now where is the magic in this? I got two methods fileBean.loadData and fileBean.saveData.

Here is what the fileDataBean class looks like:

import java.io.IOException;
import java.io.Serializable;
import java.util.Properties;

import lotus.domino.NotesException;

import com.paulwithers.openLog.OpenLogUtil;

public class FileDataBean implements Serializable {

private static final long serialVersionUID = 1L;
private String fileName;
private String fileData;
private String dbPath;
private String dbServer;

private Utils utils;
Properties props;

public FileDataBean() throws NotesException{
utils = new Utils();
utils.printToConsole(this.getClass().getSimpleName().toString() + ” – FileDataBean() // constructor”);
try {
props = utils.getDataSourceProperties();
dbServer = props.getProperty(“server_namis_notesname”);
dbPath = utils.getSession().getCurrentDatabase().getFilePath();
} catch (IOException e) {
OpenLogUtil.logError(e);
}
}

public String getDbPath() {
return dbPath;
}

public void setDbPath(String dbPath) {
this.dbPath = dbPath;
}

public String getDbServer() {
return dbServer;
}

public void setDbServer(String dbServer) {
this.dbServer = dbServer;
}

public void setFileData(String fileData) {
this.fileData = fileData;
}

public String getFileData() {
return fileData;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public String getFileName() {
return fileName;
}

public void loadData() {
this.fileData = NAPIUtils.loadFile(this.dbServer, this.dbPath, this.fileName);
}

public void saveData() {
NAPIUtils.saveFile(this.dbServer, this.dbPath, this.fileName, this.fileData);
}
}

The NAPIUtils looks as followed:

import java.io.InputStream;
import com.ibm.designer.domino.napi.NotesAPIException;
import com.ibm.designer.domino.napi.NotesDatabase;
import com.ibm.designer.domino.napi.NotesNote;
import com.ibm.designer.domino.napi.NotesObject;
import com.ibm.designer.domino.napi.NotesSession;
import com.ibm.designer.domino.napi.design.FileAccess;
import com.paulwithers.openLog.OpenLogUtil;

public class NAPIUtils {

/**
* loads a given WebContent file and returns the result as String
*
* @param serverName
* the server to use
* @param dbPath
* the database path
* @param fileName
* the file to load
* @return the file data as String
*/
static public String loadFile(final String serverName, final String dbPath, final String fileName) {
NotesSession nSession = null;
NotesDatabase nDatabase = null;
NotesNote nNote = null;
try {
nSession = new NotesSession();
// open database
nDatabase = nSession.getDatabaseByPath(serverName + “!!” + dbPath);
nDatabase.open();
// load existing data
nNote = FileAccess.getFileByPath(nDatabase, fileName);
// get Filedate and return String
InputStream is = FileAccess.readFileContentAsInputStream(nNote);

return convertStreamToString(is);
} catch (NotesAPIException e) {
OpenLogUtil.logError(e);
} finally {
// recycle NAPI objects
recycleNAPIObject(nNote, nDatabase, nSession);
}

return fileName;
}

/**
* loads a given WebContent file and returns the result as String
*
* @param serverName
* the server to use
* @param dbPath
* the database path
* @param fileName
* the file to load
* @param fileData
* the data of the file
*/
static public void saveFile(final String serverName, final String dbPath, final String fileName, final String fileData) {

NotesSession nSession = null;
NotesDatabase nDatabase = null;
NotesNote nNote = null;

try {
nSession = new NotesSession();
// open database
nDatabase = nSession.getDatabaseByPath(serverName + “!!” + dbPath);
nDatabase.open();
// load existing data
nNote = FileAccess.getFileByPath(nDatabase, fileName);
// store them to note
FileAccess.saveData(nNote, fileName, fileData.getBytes());
} catch (NotesAPIException e) {
OpenLogUtil.logError(e);
} finally {
// recycle NAPI objects
recycleNAPIObject(nNote, nDatabase, nSession);
}
}

/**
* converts an input stream to a string
*
* @param is
* the input stream to convert
* @return String
*/
static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter(“\\A”);
return s.hasNext() ? s.next() : “”;
}

/**
* recycleNAPIObject helper method for recycling NAPI objects
*
* @param nObjects
* the NAPI objects to recycle
*/
static void recycleNAPIObject(NotesObject… nObjects) {
for (NotesObject nObject : nObjects) {
if (nObject != null) {
try {
(nObject).recycle();
} catch (NotesAPIException ne) {
OpenLogUtil.logError(ne);
}
}
}
}
}

From this class I use the loadFile and saveFile methods. Look at the import of the com.ibm.designer.domino.napi classes which enables me to access files within the NSF!

Hopefully this writing gives you some new ideas for your development projects. Happy coding 🙂