Lotus Notes

You know, Lotus Notes is the only application I know of where the native client can suddenly decide not to draw any menus, until you restart...

Howto: Setup a secure area

Yesterday I was tasked to setup a secure area for a customer (or rather I was tasked to "fix" it, as it was not working). Digging in, I assumed it was an error somewhere, but it turns out everything is working as designed.

The "problem" is that you can mark a page as secure by giving access to "External Readers". This will set a readers field with that role. For those of you who doesn't know Domino security in detail, this will in practice make the publication invisible to users without access.

security1.png

As an anonymous user, the system does not know anything about this publication, it is simply missing, so the system returns a 404, when the expected result was a login-form. This is different from "classical" Domino development, where the system will automatically throw a loginpage at you if it detects a readers field. In iSite, the user requesting the publication cannot see it, and cannot check if it has any readers fields.

I said earlier, that I assumed it was an error, but turns out it is easy to "fix" by using 2 built in features.

1.) Create a login document, name it "Login" (or whatever), and set the url to something like "external/login"

login.png

2.) In the security tab, select "Alle påloggede" ("All authenticated"). This will force a login-dialog.

3.) Create your real protected area, and fill it with the information you want kept secret. Use the same url, and make sure you choose the "Alle eksterne lesere" (All external readers), or whatever users you want.

protected.png

4.) In the "login"-publication, go to "Vis denne artikkelen istedet" (Show this publication instead), and select the "Secret"-publication.

instead.png

Make sure both are published, and voila, there you go. This works because the "Login" masks the real area, and once you are logged in, the system will either use the real url and open the document, *or* it will use the "Login" publication, but just replace the content with the content from "Secret".

PS! We are working on an english version, stay tuned.

Events: OnAfterResult

In the series of showing of (and internally testing) the event and plugin system, I am going to show of the OnAfterResult event.

OnAfterResult is triggered after the controller has done its job, but before the result is returned to the browser. This means we have the complete Html-page available as a String. A good use of this event, is for example to change image urls on mobile versions of pages (by replacing 'medium_' with 'small_' in the image urls).

Another usage is to change text-phrases. We have a client who at one time changed their company name from CamelCase to UPPERCASE, and wanted to change the webpage accordingly. That would have been a good time to have the plugin system available (while the client renamed everyting at their leisure)

Below is an example that changes the title 'TEST1' to something else.

package no.wpc.isite.test

import no.wpc.isite.mvc.*

@Plugin(event='onAfterResult', scope='no.wpc.isite.osgi.publisher.controllers.Page')
class OnAfterResultTest implements IEvent{

    @Override
    public void execute(String event, String scope, IController controller, Object... args) {
        def resultWrapper = args[1]
        
        def pattern = ~/<h1>TEST1<\/h1>/
        
        resultWrapper.result = resultWrapper.result.replaceAll(pattern, '<h1><blink>TESTING STUFF</blink></h1>')
    }

}

The 'resultWrapper' is a Map with a key named 'result', this is to allow the value to be changed.

Btw, the <blink>-tag works fine in Html5 ;)

Quote of the day

"Scientists decorated red blood cells with gold nanoparticles so they could trigger the cells to dump their contents with a zap from a laser. The laser pulses heated the particles to produce nanopores in the cells' membranes. The cells contained two fluorescent dyes and both flooded through the pores and out of the cells after the laser pulses. Although the researchers studied the release of dyes, their end goal is to use red blood cells as a vehicle for drug delivery, because the cells are naturally compatible with the immune system and circulate for days in the body. Until now, researchers have found easy ways to load the cells with drugs, but the challenge has been to control the molecules' release."

These are exciting things we are living, when our medicine is starting to look a lot like magic :)

Built-in Events in iSite Core

These are the list of built in events in iSite Core:

  • onBeforeController - runs before the Controller is invoked
  • onAfterController - runs after the Controller has finished, but before template is invoked
  • onAfterResult - runs after the template has done it's work
  • onBeforeLoad - runs before a Document is loaded from the database
  • onAfterLoad - runs after a Document is loaded from the database
  • onBeforeDelete - runs before a Document is deleted
  • onAfterDelete - runs after a Document has been deleted
  • onBeforeSave - runs before a Document has been saved
  • onAfterSave - runs after all fields have been set, but right before Document is committed to database
  • onAfterSaved - runs right after Document is committed to database
  • onAppStart - runs when the server is started
  • onAppStop - runs when the server is stopped

All events take a scope, that is usually set to a class-name. For on*Load, on*Save, on*Delete, its the name of the IModel that invokes them, for the others, its the name of the controller (IController). This way you can scope your plugins to only run when needed, for example, if you have a plugin to be run onBeforeSave, but only on your custome "FancyModel", you use the @Plugin to limit it's scope:

@Plugin(event="onAfterSave", scope="com.acme.mvc.models.FancyModel")

If you don't set a scope, a global scope "*" is used, then you need to filter yourself in your plugin (this should mostly be used for Core plugins)

The events take a varying number of parameters, but the method signature looks like this:

execute(String event, String scope, IController controller, Object... args)

These are the events, and their parameters

plugin.trigger(String scope, 'onBeforeController', IController controller, FacesContext facesContext)
plugin.trigger(String scope, 'onAfterController', IController controller, FacesContext facesContext)
plugin.trigger(String scope, 'onAfterResult', IController controller, FacesContext facesContext, Map resultWrapper)
plugin.trigger(String scope, 'onBeforeLoad', IController controller, Document doc, Map result)
plugin.trigger(String scope, 'onAfterLoad', IController controller, Document doc, Map result)
plugin.trigger(String scope, 'onBeforeDelete', IController controller, Document doc)
plugin.trigger(String scope, 'onAfterDelete', IController controller, String unid)
plugin.trigger(String scope, 'onBeforeSave', IController controller, Document doc)
plugin.trigger(String scope, 'onAfterSave', IController controller, Document doc)
plugin.trigger(String scope, 'onAfterSaved', IController controller, Document doc, boolean saved)
plugin.trigger("*", "onAppStart", null, ServletConfig config)
plugin.trigger("*", "onAppStop", null, ServletConfig config)

You can of course implement your own plugins, to make your own extention points (and it's simple).

package com.acme

import no.wpc.isite.PluginManager
import com.acme.Rockets

class AcmeRocket {
    private PluginManager plugin = PluginManager.getInstance()

    void init(){
        plugin.trigger('*', 'onBeforeLaunch', null, Rockets.ExplodingWarhead, new AcmeBuyer())
    }
}

And a plugin implementing the event:

package com.acme.events

import no.wpc.isite.mvc.IController;
import no.wpc.isite.mvc.IEvent
import no.wpc.isite.mvc.Plugin
import com.acme.Rockets

@Plugin(event="onBeforeLaunch")
class OnBeforeLaunchAcme implements IEvent{

    public void execute(String event, String scope, IController controller, Object... args) {
        String warhead = args[0]
        IAcmeBuyer buyer = args[1].getCoyote()

        IRocket rocket = RocketFactory.getRocket(warhead)
        rocket[5..1].launch()

        buyer.sendBill()
    }
}