This last couple of weeks I've made what I hope to be a brief return to developing for the Notes client. One of the requirements I had was to "add a form" to an existing Notes database, which allowed the user to input a few parameters and do a simple search of a remote SQL database, present the results to the user for their review before letting them import the resulting data in to the Notes database.
It goes without saying that I'd forgotten how to program in the Notes client. Having had such a long absence from Notes I came at it assuming that I could use Java code in the client (maybe you can and I missed something?).
For example, I'd assumed that, by now, I'd be able to write Java code in the Click event of a Button. As you can see below, this isn't an option.

I'm sure there's good reasons why we can't (is there?) but it left me a bit stuck. Was LotusScript my only option? I didn't relish the idea of fudging my way through getting LotusScript to do what I needed to do. It's not that I don't think it could. I'm sure it can, but it was a problem that really needed Java as the solution.
Not wanting to learn client development all over again or struggle trying to get LotusScript to do it I opted to write a Java Swing-based interface.
You can see an example below:

In the above example the user has clicked the Action button on the view and up popped the interface you can see. After entering some data and clicking a button the results table is populated from the SQL data. Then, when the Import button is pressed the data is saved in Notes -- a new document for each row of the table.
How does it work?
It's quite simple. The View's Action button looks like this:

This runs a Java Agent called "Import". Inside this agent the Java code simply creates an instance of a JFrame, adds all the components to it and then displays it. As far as the user's concerned it's just a part of Notes - when in fact it's a "standalone" Java application running inside Notes' JVM.
Powerful
The more I got in to this approach the more powerful I realised it was. Not only because the Java code is all self-contained in an Agent; meaning there are no installers or separate executable files to mange or distribute. But also because the app gets updated at the same time as the database design. Even the icons used in the buttons are stored as Resource files in the Agent itself. It's win win.
And because it's written in pure Java it's uses true OO patterns and a rich GUI. There are endless possibilities and pretty much no limit as to what you can do inside that popup window. I even managed to use a Thread to add and update a progress bar during the import process, which made me feel all geeky going multi-threaded (easier than it may sound!).
It also used a Data Access Layer (DAL) and data-modelling classes to abstract away from the SQL database. I had to do this as it's schema is subject to change. Also, at some point, the system will be moved off of Notes. At that point it will be easier to migrate this pure OO Java-based solution than it would be a Notes one.
It's worth noting that it runs inside the Notes JVM as the current user, meaning there's no need to store or ask for their password.
Gotchas
The one thing I don't like about using these Java windows is there's no way to open them as modal to the Notes client. It's all to easy for them to slip under the Notes client and the user to lose track of where it's gone.
However, they can be made to run (and stay) on top of all other windows, but that's a bit annoying. To do that would involve editing the java.policy file (found in <Notes Folder>\JVM\lib\security) on the user's machine and adding this permission:
permission java.awt.AWTPermission "setWindowAlwaysOnTop"; Then in the code itself you can set the Swing frame to stay on top, like so:
try{
frame.setAlwaysOnTop(true);
} catch (SecurityException sex){ }
Also, I've I've yet to work out how to limit it to only showing one window at a time. If the windows pops below others and the user presses the View Action button again they end up with two of them. I'd rather it brought the first back in to focus.
The other gotcha I found was that any button click (or other event) handlers in the popup window had no access to the Agent's Session object. That's because the Agent itself has completed running and all the session-based object have been recycled. If you want the Java code to interact with Notes then you need to create a new session.
To do this I wrote the basis of the Agent like this:
public class JavaAgent extends AgentBase {
private static String databaseServer;
private static String databasePath;
public void NotesMain() {
try {
Session session = getSession();
databaseServer = session.getCurrentDatabase().getServer();
databasePath = session.getCurrentDatabase().getFilePath();
} catch(Exception e) {
e.printStackTrace();
}
}
private static void calledFromSwingUIAfterAgentIsDone(){
try{
NotesThread.sinitThread();
Session session = NotesFactory.createSession();
Database db = session.getDatabase(databaseServer, databasePath);
Document doc = dc.createDocument();
} catch (Exception e){
} finally {
NotesThread.stermThread();
}
}
}
Note that I stored the database's server name and file path in two Strings that we can refer to later when we create our own Session object and re-open the database.
Summary
All in all it's an approach I'm happy with and it's got the job done. Some of you may think it's crazy overkill but, for me, it was the best (if not only) solution.
If there's any interest I can package up a downloadable example?
3/8 Comments Read - Add | Fri 18 May 2012 |
| Open »
A while back I talked about how I'd been using Java Swing components within code running inside the Lotus Notes client to "extend its interface" however I liked.
As an example, I showed this progress-bar-enabled output window:

It works but looks a bit too Java-ery for my liking. Too obviously not a standard part of the client itself. It just jarred with me ('scuse the Java pun there).
Well, if you add the following line in to the Java code before opening the window above:
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
You get something that looks like this. All together a bit better looking.

When this appears after clicking a button inside a database inside the Notes client you can be forgiven for thinking it was a natural part of the Notes UI. Almost.
2/6 Comments Read - Add | Mon 14 May 2012 |
| Open »
As a Domino developer, when you start to learn SharePoint, the first things that strikes you is how similar the two are. Well, they're quite different, but there are undeniable parallels.
As I work my way through this book I am compiling a table of the elements that make up SharePoint and trying to match them to Domino design elements I'm familiar with. There's no real point to it other than try and understand SharePoint from a perspective I'm used to.
Here's the table as it stands (very much a draft):
| Domino Element | SharePoint Element | Comments |
| Domino Directory | Active Directory | Other way to store users? Dynamics? |
| | Web Application | |
| | Site Collection | |
| Database | Site? | Although there can be multiple Sites per SQL "content database" so maybe a Site Collection is more akin to a databases? |
| ACL | Site Permissions | |
| User | AD User | |
| Form | Content Type? | A Content Type combines a group of Site Columns and defines the pages used to edit/view them. |
| Subform | Web Part? | |
| View | List | |
| Agent (WQO and WQS) | WorkFlow / Event Receiver? | |
| Agent (via Action menu) | Custom Action (Ribbon-based) | There are options in SharePoint to get a handle on selected documents too. |
| Scheduled Agent | Timer Job | More on Timer Jobs |
| Field | Column | |
| Shared Field | Site Column | |
| Computed Field | Computed Column | Hidden/Read Only Site Column perhaps? Accessed via object model |
| Readers Field | FGP? | FGP=Fine-Grained Permissions (link) |
| Authors Field | FGP? | |
| Document | List Item | |
| Profile Document | Property Bag | Somewhere to keep "settings" and configuration. Various Options |
| Action Bar | Ribbon | Not as cut and dry but a simple comparison to make. Ribbon is way much more than a |
| Page | Content Page | Or maybe an Application Page? |
| Script Library | *.cs files | In Visual Studio you can create your own assemblies using as many and as complex a set of C# source files as you like. |
| File Resources/Images etc | Module | |
| | Feature | Can contain lots of combined objects such as ContentType, CustomAction, WebTemplate, Field etc |
| | Package | |
| | Solution | Combines lots of Features? |
| Mail-In Database | List | You can specify a mail-in email address for any List |
If you have any input or comments please add them below and I'll amend the table accordingly.
There are some gaps where, as far as I'm aware, there's no equivalent in the other system. If I'm wrong please say! Any SharePoint element with a question mark next to it are those I'm not confident about.
11/25 Comments Read - Add | Wed 9 May 2012 |
| Open »
Here's the explanation and the solution to the Java applet issue I mentioned yesterday.
As you saw the page combined a Domino Java View applet and an ActionBar applet. They are two separate applets. They talk to each other using JavaScript. You can see how by looking at the source to the page.
Clicking the Refresh button on the applet calls this JavaScript function:
function Action1_onClick() {
document.applets.view.refresh();
return false;
}
That code is calling an exposed method of the View applet called "refresh()". And it's failing. Why?
Well, having isolated the issue to the Java code I needed to get to see the Java console in the hope that any errors were dumped there.
I'd previously told Java not to add an icon to the Task Tray in Windows. If you do see the Java icon down there you can right click it and choose "Show Console".
After some digging I found out how to get to see the Java console. All you do is go to the Control Panel, open the Java options from there and make sure the following options are selected:

Next time you open the webpage with the Applets you should see the console appear and start logging, like so:

If it appears but with less logging detail, focus on the console and press "5" to set the logging level to verbose and reload the page.
With the console on display I then pressed the Refresh button again and "thankfully" saw an error, as below:
network: Connecting http://www.codestore.net/apps/applets.nsf/view?ReadViewEntries&PreFormat&Start=2.1&Navigate=15&Count=40&SkipNavigate=32784&SkipCount=2 with proxy=DIRECT
An error occurred while reading in documents: java.security.AccessControlException: access denied (java.net.SocketPermission 63.254.226.99:80 connect,resolve)
java.security.AccessControlException: access denied (java.net.SocketPermission 63.254.226.99:80 connect,resolve)
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkConnect(Unknown Source)
at sun.plugin2.applet.Applet2SecurityManager.checkConnect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at sun.net.NetworkClient.doConnect(Unknown Source)
at sun.net.www.http.HttpClient.openServer(Unknown Source)
at sun.net.www.http.HttpClient.openServer(Unknown Source)
at sun.net.www.http.HttpClient.<init>(Unknown Source)
at sun.net.www.http.HttpClient.New(Unknown Source)
at sun.net.www.http.HttpClient.New(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.postUNIDList(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.postUNIDList(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.ReadAndParseViewEntries(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.ReadLineRange(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.refresh(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.refresh(Unknown Source)
at lotus.notes.apps.viewpanel.ViewPanel.deleteMarkedDocuments(Unknown Source)
at lotus.notes.apps.viewapplet.ViewApplet.deleteMarkedDocuments(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.plugin.javascript.JSInvoke.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor7.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.plugin.javascript.JSClassLoader.invoke(Unknown Source)
at sun.plugin2.liveconnect.JavaClass$MethodInfo.invoke(Unknown Source)
at sun.plugin2.liveconnect.JavaClass$MemberBundle.invoke(Unknown Source)
at sun.plugin2.liveconnect.JavaClass.invoke0(Unknown Source)
at sun.plugin2.liveconnect.JavaClass.invoke(Unknown Source)
at sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$DefaultInvocationDelegate.invoke(Unknown Source)
at sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo.doObjectOp(Unknown Source)
at sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$LiveConnectWorker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
At this point I'd been trying all kinds of other approaches to debugging this and had spent hours on it. I've never been so grateful to see an error.
What I still didn't get was why the applet could load the view's content on the first page load - this was a major source of confusion.
Why was there an "access control" issue only when it reloads the view!?
After some searching and lots of reading (and lots of red herrings) I found out that since Java 6 reached "Update 22" a new security feature was implemented. The fix meant that any Java applet making a URL connection which was the result of being asked to do so by a JavaScript call had to do so with the same security settings/permissions as the JavaScript itself.
That's when the applet goes all paranoid! In doing so it wants to rule out the chance it's the target of cross-domain security breach.
But again, things are odd here. It's not cross domain. The applets are loaded from the same domain as the web page itself. As are the subsequent calls to the XML data via ?ReadViewEntries! Why does it think it's cross domain.
The next step and the eureka moment was in working out how the applet decides if it's cross domain or not.
It all comes down to whether or not a "reverse DNS lookups" of the resolved IP address points to the same domain name as shown in the address bar.
To work this out you first need to ping the domain in use, like so:

Then I pinged the resolved IP address directly while including the "-a" parameter (resolves addresses to hostnames) and you can see the result below:

Et voila! A reverse DNS lookup for codestore.net points to something completely different! That is why the Java applet breaks!!
To corroborate this I further tested in my own local network on my server called "dover" and resolvable in DNS both as dover and as dover.rockalldesign.com.
I then tried using the applets at the following URLs:
- http://dover/apps/applets.nsf/view
- http://dover.rockalldesign.com/apps/applets.nsf/view
The Refresh button worked on the first URL but not the second. Why? Consider the following:

On my local network a Reverse DNS Lookup for "dover.rockalldesign.com" doesn't match the expected name of "dover", so the 2nd URL fails. But as you can infer from the above a reverse lookup for "dover" does returns a matching name, so the Java applet is happy to continue loading for the 1st URL.
Solution
The obvious solution would be to make sure you used a domain name in your URL with matches the one returned by a reverse lookup. In the actual customer's case this was a viable solution, as it is for my dover server. But not for the codestore.net version as the mcleodusa.net address it points back to can't be resolved in the address bar.
The alternative (and probably the better) solution is to add a cross-domain.xml file at the root of the HTTP server, so it would be accessible at http://www.codestore.net/cross-domain.xml. The content of the file is simply:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>
This will let you access the database using any domain / host which points to the server.
Summary
My customer's response was "Jake, you're a genius!". Well, as much as I'd love to agree, I wouldn't go quite that far. All I am is a patient nerd with enough experience of computers to know how to second guess them. Some times with problems like this it's down to a simple combination of experience and gut instinct.
The fact it's only going to affect users of the Domino View Java Applets made me wonder if it's even worth sharing this (who in their right mind still uses them?!). But then the fact I was asked and then paid to solve it in the first place means people obviously still must use them.
4/5 Comments Read - Add | Fri 4 May 2012 |
| Open »
Yesterday I debugged a particularly nasty issue. The solution to which I thought I'd share with the world. Before I do I thought it would be fun to reproduce the problem, by way of a challenge for you guys.
Fancy yourself as a half decent hacker? Take a look at the the following link:
http://www.codestore.net/apps/applets.nsf/view
Pressing the Refresh button on the Action Bar should simply refresh the view. It doesn't. It just empties it. See if you can work out why. Muhaha.
Your only clue: it has nothing to do with the Domino side of it!
Update: Some of you have noticed it's not broken. Therein lies clue 2: it won't affect all of you.
9/22 Comments Read - Add | Thu 3 May 2012 |
| Open »
More blog entries are available in the archive »