Domino-Migration: Der frühe Vogel fängt den Wurm

Wenn die Entscheidung erst einmal gefallen ist, dass mittel- oder langfristig die Domino-Infrastruktur aus dem Unternehmen verschwinden wird, lässt die Investitionsbereitschaft der einzelnen Fachabteilungen in Domino-basierte Applikationen verständlicher Weise spürbar nach. Aber auch aufkommende Gerüchte oder durchgeführte Studien (auch wenn diese zu einem gegenteiligen Ergebnis kommen) können den Willen der IT- oder Geschäftsleitung nach einem Systemwechsel zu entsprechender Aussage verhelfen, und durch die aufkommende Ungewissheit zu dem gleichen Ergebniss führen.

Um dennoch zukunftssichere Applikationen zu entwickeln, bieten sich verschiedene Wege an, die sich (in Abhängigkeit zu der bestehenden und der zukünftigen Infrastruktur sowie der gewünschten strategischen Ausrichtung) jedoch stark unterscheiden können. Für jedes Unternehmen sind bestimmte Faktoren unterschiedlich zu bewerten, daher gibt es keinen “Königsweg”: Der zu beschreitende Weg ist immer individuell, und es müssen höchstwahrscheinlich ein paar Kompromisse eingegangen werden, um einen Übergang so geräuschlos wie nur möglich über die Bühne zu bringen.

Der mit Abstand wichtigste Punkt ist, sich möglichst früh vom liebgewonnenen Domino zu verabschieden, ohne eine falsch angebrachte Sentimentalität. Weitere wichtige Punkte, die es meiner Erfahrung zu beleuchten gilt, sind

  1. die gewünschte Form der Migration
  2. mögliche Ziel-Plattformen
  3. zunkünftige Datenhaltung
  4. die Authentifizierung der Anwender

Formen der Migration

Ist eine harte Migration gewünscht, sind praktisch alle Entscheidungen bereits getroffen, und eine weiteres Auseinandersetzen mit anderen Szenarien erübrigt sich: Sowohl Zielplatform als auch (Wunsch-)Termin stehen fest. Das Budget spielt dann wahrscheinlich auch keine Rolle, und ich wünsche viel Glück und gutes gelingen.

Wählt man hingegen eine weiche oder integrative Migrationen, erhöht sich der nötige Spielraum deutlich, und die Chance einer erfolgreichen Umstellung steigt, denn der Zeitdruck und das damit einhergebrachte Risiko eines Fehlschlages wird durch die Politik der kleinen (aber feinen) Schritte drastisch verringert.

Mögliche Zielplattformen

In einem Microsoft-lastigen Unternehmen bietet sich natürlich der IIS als Webserver an, um Applikationen darauf zu hosten. Der IIS unterstüzt neben den “Microsoft-eigenen” Sprachen wie .NET auch weitere wie z.B. Java und PHP. Selbst Node.js wird unterstüzt.

Hier wäre also ein Ansatzpunkt, neue Applikationen direkt auf dem IIS zu betreiben. Falls momentan noch kein IIS im Einsatz ist, bietet es sich an, eine PHP-Applikationen auf dem Domino zu hosten, und diese dann später auf den IIS zu verschieben. Als Alternative kann auch ein Apache HTTP Server oder ein Tomcat dienen, wenn IT diesen betreiben kann (und will). Gleiches gilt natürlich auch für Node.js. Letztlich muss dieser Punkt mit der Administration abgeklärt werden.

Zieht man Client-basierte Applikationen (Stichwort “Angular JS”) in Betracht, kann die Applikation sogar in die Cloud verlagert werden, denn die Daten bleiben im Unternehmen: Zwar lädt der Browser die statischen Ressourcen aus der Cloud, doch werden die Daten vom Browser nur per REST-Schnittstelle im Unternehmensnetzwerk hin- und hergeschoben. Dadurch entfiele ein entsprechender administrativer Aufwand, die Applikation könnte auch von einem externen Anbieter problemlos betrieben werden (z.B. Bluemix).

Zukünftige Datenhaltung

Die Datenhaltung stellt wohl in bestimmten Stitautionen die größte Herausforderung dar, insbesondere wenn auch alte Daten migriert werden müssen. Gerade das Thema “Rich Text-Felder” kann Probleme bereiten, denn je nach Verwendung wird es schwierig, bestehende Daten/Funktionalitäten 1:1 zu übernehmen. Auch einige eher selten genutzte Features auf Feldebene können eventuell nicht ohne erheblichen Aufwand abgebildet werden, doch muss man hier derweilen auch hinterfragen.

In den meisten Fällen sollte eine Umstellung allerdings Reibungslos möglich sein. Moderene Dokumentenbasierte Datenbanksysteme bieten einen ebenbürtigen Funktionsumfang, meist sogar noch deutlich mehr. Dennoch sollte meiner Erfahrung nach dieser Part nicht unterschätzt werden.

Authentifizierung

Eine wichtige Rolle stellt die Authentifizierung im Unternehmen dar: Wurde dies bisher vom Domino Server gehandelt, fällt diese Option zukünftig weg. Aber auch hierfür gibt es unterschiedliche Ansätze, je nachdem, woher die Applikation Ihre Daten erhält und wohin diese Daten abgelegt werden.

Wird der Domino Server nur zur Authenifizierung und nur einige Daten über den User benötigt, existiert mit LDAP eine relativ simple Lösung des Problems, die auch einfach migriert werden kann.

Sollen existierende Datenbanken angezapft werden, kann dieser Zugriff im Usercontext durch eine Proxy-Funktionalität geschehen (durch das Abgreifen/Durchschleifen des Domino Cookies). Eine spätere Umstellung kann dann für die Endapplikation transparent erfolgen, in dem letztlich der Code im Backend geändert wird, ohne das es hier zu Einschräkungen kommt.

Nutzer der Applikation

Ach, ein Thema habe ich bewusst aussen vor gelassen, dass aber ebenfalls eine große Rolle spielt: Die Nutzer der Applikation. Man darf Sie nie vergessen, und man muss versuchen, auf sie einzugehen, aber leider ist das die große Unbekannte, die praktisch unberechenbar ist: Es gibt Mitarbeiter, die sehr interessiert sind, und bei der Umstellung mitarbeiten. Gerade wenn die Möglichkeit dafür genutzt wird, das eine odere andere hilfreiche Zusatzfeature zu implementieren, und so den Mitarbeitern eine entsprechende Motivation zu bieten, wird verläuft eine Zusammenarbeit meist harmonisch und ertragreich. Letztlich sollte die Chance auf ein Redesign einer alten Applikation so gut wie möglich genutzt werden.

Aber man wird eventuell auch auf eine generelle Ablehnung stoßen, denn es war für die Mitarbeiter bisher möglich, viele Probleme im Arbeitsalltag auf “das Sch..ss Notes” zu schieben. Diese Option entfällt zukünftig – was manchem Mitarbeiter in Erklärungsnot bringen könnte, und daher wird gemauert ohne Ende. Auch könnte es vorkommen, das vorhandene Features, die noch nie funktioniert haben, über Nacht zu Keyfeatures mutieren und deren “mangelnde Funktionalität” die ganze Migration ad absurdum führen würden.

Da der “menschliche Faktor” unberechenbar und damit unplanbar ist, kann (und sollte) er in der anfänglichen Planun nicht näher beachtet werden.

Fazit

Alles in allem lösbare Punkte, auf die ich im Einzelnen in den nächsten Tagen näher eingehen möchte. Es stehen noch Themen auf der Agenda, wie beispielsweise die Suche nach relevanten Applikationen, die migriert werden müssen. Und natürlich sollte die eine oder andere Anekdote, die ich in den letzten Jahren gesammelt habe, nicht fehlen.

Veröffentlicht unter Migration | 5 Kommentare

Amazon Instant Prime: Es nervt

Jeden Sonntag das gleiche Spiel: Ab 20.00 Uhr bricht das Streaming regelmäßig zusammen. Trotz sehr guter Internetverbindung (hier ein Screenshot vom Smartphone, per WLAN in einer “miesen” Ecke in meinem Haus):

Einer der größten Cloud-Anbieter der Welt bekommt es einfach nicht gebacken, hoch zu skalieren. Es gab Zeiten, da hatte ich diese Problem nicht: Anklicken, runter laden, in Ruhe anschauen. In bester Qualität, kurz nach Kinostart. Seit ich für diese Leistung bezahle, funktioniert das nicht mehr…

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

XPages: Auf’s falsche Pferd gesetzt

Gerade eben ist mir klar geworden, dass das letzte XPages-Projekt (eine Mini-Entwicklung) schon ein halbes Jahr zurück liegt. Und wenn ich mir das Gesamtvolumen in 2014 mit knapp 30 Tagen Entwicklungsleistung für diese Technologie anschaue, muss ich mir wohl eingestehen, auf das falsche Pferd gesetzt zu haben. In anderen Regionen Deutschlands scheint die Situation etwas anders zu sein, doch als Freiberufler kann man sich seinen Markt leider nicht aussuchen. Schade eigentlich.

Wenn ich allerdings über das “Warum” nachdenke, ist das alles eigentlich gar nicht so überraschend: Die “alten Hasen” im Notes Business sind von der Technologie förmlich überrollt worden, und haben die ersten Projekte mehr schlecht als recht umgesetzt.

Zu viel neue Themen, alles so kompliziert, und im Notes Client ist eh alles besser… Erst neulich war ich Zeuge eines Gesprächs zwischen Entwickler und Projektleitung, dass man die Aufwände, eine XPages Applikationen zu entwickeln, gegenüber denen einer Notes Client Entwicklung mit dem Faktor 3(!!!) kalkulieren müsse.

Und die, die sowieso keine Lust auf die Technologie hatten (aus Angst um den eigenen Job, da man nicht mehr hinterher kommt), haben solange das Thema XPages schlecht geredet, bis es die Kunden auch geglaubt haben. Gut, Antworten auf drängende Fragen konnte keiner der Betroffenen liefern (wie man denn z.B. die tollen “native” Notes Applikationen auf dem iPad nutzen kann). Es offenbarte jedoch die offensichtlich fehlende Weitsicht, dass dies der Platform in den Unternehmen lang- bzw. mittelfristig mehr schadet als nützt. Aber was soll’s…

Letztlich muss sich keiner wundern, wenn potentielle Kunden abspringen. Und ja, IBM und Marketing sind Begriffe, die nichts mit einander zu tun haben.

Aber, liebe “alte Notes Hasen”, die IBM ist nicht an allem Schuld! Ihr habt durch Eure Blockadehaltung auch einen nicht geringen Beitrag geleistet, die Platform klein zu kriegen. Die mangelnde Flexibilität einiger Admins, die Java für Teufelszeug halten und mir Ihrer “Sicherheit geht über alles”-Philosophie sich sämtlichen Errungenschaften seit vor der Jahrtausendwende verschließen (Jaja, Usenet & Gopher sind schon toll), hat der Platform mindestens so sehr geschadet, wie die “große Unbekannte” durch die Produktstrategie der IBM. Also zeigt nicht immer mit dem Finger auf die Buchstaben I,B & M. Probiert es mal mit einer Schuldzuweisung bei I, C & H!

So, dass musste raus. Jetzt feile ich fleissig weiter an meinen Migrationsstrategien…

Veröffentlicht unter Allgemein | 12 Kommentare

Migration der Domino-Infrastruktur

In den nächsten Wochen werde ich mich intensiv mit der Migration bestehender Domino Infrastrukturen und den vorhandenen Applikationen befassen. Mich interessieren die unterschiedlichen Wege und die möglichen Fallstricke, die es zu beachten gilt, und da ich im letzten Jahrzehnt den einen oder anderen Einblick hatte, ist es an Zeit, ein Resume zu ziehen und die gesammelten Erfahrungen aufzubereiten.

Wer jetzt allerdings erwartet, dass es dabei um die Aufzählung von negativen Erlebnissen geht, möchte ich enttäuschen, denn das ist nicht mein Ziel: Mir geht es darum, einen neuen Weg bei der Applikationsplanung und -entwicklung einzuschlagen (oder den bereits gegangenen zu bestätigen), und um meinen Kunden verschiedene Szenarien anbieten zu können.

Zunächst möchte ich auf das Thema “friedliche Koexistenz” eingehen, bei der neue Applikationen an die an die bestehende Domino-Infrastruktur angedockt werden. Welche Technologien existieren hier, und welche Möglichkeiten gibt es? Und was muss bei der Entwicklung bedacht werden, wenn sich die Infrastruktur von einem auf den anderen Tag ändert?

Um die getätigten Investitionen zu schützen, darf das Thema “Datenübernahme in andere Systeme” natürlich nicht fehlen, und auch nicht die damit verbundenen Fallstricke. Bei meinen Überlegungen möchte ich auch nicht von einer Insellösung auf die nächste Insellösung umzuziehen, sondern ich möchte dabei möglichst Technologieunabhängig und Zukunftssicher bleiben.

Ein Gedankenspiel also, wie es in der Realität niemals vorkommt. Aber falls es jemals dazu käme, dass die Zielplattform noch nicht feststeht, möchte ich gewappnet sein und eine passende Antwort liefern können.

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

MongoDB for DBAs

After receiving my confirmation for MMDS today, I also received the confirmation for successfully completing the “MongoDB for DBAs” course:

Here is the link to verify the certificate.

Veröffentlicht unter MongoDB | Verschlagwortet mit | 2 Kommentare

Mining Massive Datasets

I am very proud that I have successfully accomplished the MMDS course from Stanford University. It was unbelievable interesting to learn the theories and the mathematical basics of  topics like MapReduce, Web-link analysis, Data-streams, Locality-sensitive hashing, Computational advertising, Clustering, Recommender systems, Analysis of large graphs, Decision trees, Dimensionality reduction, Support-vector machines, and Frequent-itemset analysis.

It was really hard for me to follow all the topics, because I started to late (in the third week of the course), had a lot of work to do for my customers and have not seen any linear algebra since at least a decade. I had only the the nights to learn and to do the required homework. During the final examen,  my complete family was ill and I have to care them.

But in the end I did it:

Veröffentlicht unter Big Data | Verschlagwortet mit | 1 Kommentar

XPages: WebContent Files (3) – Create a Minimizer Servlet

Because of Stefano Fois comment I decided to write an example about how to create a minimizer servlet for Domino which compresses JavaScript resources on the fly. This is, again, a simple Proof-Of-Concept, nothing more and nothing less.

First, I downloaded the YUICompressor, a Java based minimizer for JavaScript code from the project page. There are other compressors outside, I decided to use this one because it was the first result in my StartPage.com search.

The project is a single jar file and can be easily imported into an existing Domino database, in my case to my demonstration NAPI.nsf.

The next step is to create a servlet and a servlet factory, the basics are described here. To enable the servlet, an additional service file is required.

The file com.ibm.xsp-adapter.servletFactory contains the name of the factory class:

ch.hasselba.jsf.servlet.MinimizerServletFactory

The servlet factory is simple, it just defines the servlet class to use and the path which the servlet listens to requests:

package ch.hasselba.jsf.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import com.ibm.designer.runtime.domino.adapter.ComponentModule;
import com.ibm.designer.runtime.domino.adapter.IServletFactory;
import com.ibm.designer.runtime.domino.adapter.ServletMatch;

public class MinimizerServletFactory implements IServletFactory {

    private static final String SERVLET_WIDGET_CLASS = "ch.hasselba.jsf.servlet.MinimizerServlet";
    private static final String SERVLET_WIDGET_NAME = "JS Minimizer";

    private ComponentModule module;

    public void init(ComponentModule module) {
        this.module = module;
    }

    public ServletMatch getServletMatch(String contextPath, String path)
            throws ServletException {

        String servletPath = "";

        if (path.contains("/minimizer")) {
            String pathInfo = path;
            return new ServletMatch(getWidgetServlet(), servletPath, pathInfo);
        }

        return null;
    }

    public Servlet getWidgetServlet() throws ServletException {
        return module.createServlet(SERVLET_WIDGET_CLASS, SERVLET_WIDGET_NAME, null);
    }

}

The servlet is now reachable by the following URL

http://example.com/path/to/db.nsf/xsp/minimizer/

How does the minimizer servlet work? It appends all required files into a single string and compresses the string before sending the result to the browser. So the first information needed is which files should be used. This can be done with the parameter “files“, the list of files is concatenated with “+“:

http://example.com/path/to/db.nsf/xsp/minimizer/?files=file1.js+file2.js+ ...

The given files are then loaded via NAPI into a large StringBuffer and then compressed and mimimized by YUI and GZIP.

package ch.hasselba.jsf.servlet;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;

import ch.hasselba.napi.NAPIUtils;

import com.ibm.xsp.webapp.DesignerFacesServlet;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;

public class MinimizerServlet extends DesignerFacesServlet implements
        Serializable {

    private static final long serialVersionUID = -1L;

    @Override
    public void service(ServletRequest servletRequest,
            ServletResponse servletResponse) throws ServletException,
            IOException {

        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        ServletOutputStream out = servletResponse.getOutputStream();

        try {
            res.setContentType("application/x-javascript");
            res.setCharacterEncoding("utf-8");
            res.addHeader("Content-Encoding", "gzip");

            // load the js requested files
            StringBuffer fileData = new StringBuffer();
            String tmpFile = "";
            String paramFiles = req.getParameter("files");
            String[] files = paramFiles.split(" ");

            // and add them to a buffer
            for (String file : files) {

                try {
                    tmpFile = NAPIUtils.loadFile("DEV01", "NAPI.nsf", file);
                    fileData.append(tmpFile);
                } catch (Exception e) {
                    // ignore errors
                    e.printStackTrace();
                }

            }

            // Compress the JS Code with compressor
            StringWriter sWriter = new StringWriter();
            compress(stringBufferToInputStreamReader(fileData), sWriter);

            // and GZIP it
            ByteArrayOutputStream obj = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(obj);
            gzip.write(sWriter.toString().getBytes("UTF-8"));
            gzip.close();

            // send it to the client
            out.write(obj.toByteArray());

        } catch (Exception e) {
            e.printStackTrace(new PrintStream(out));
        } finally {
            out.close();
        }
    }

    /**
     * Helper to convert a StringBuffer to an InputStreamReader
     * 
     * @param strBuffer
     *            the StringBuffer to convert
     * @return the converted InputStreamReader
     */
    public static InputStreamReader stringBufferToInputStreamReader(
            final StringBuffer strBuffer) {
        return new InputStreamReader(new ByteArrayInputStream(strBuffer
                .toString().getBytes()));
    }

    /**
     * compresses the JS code using YUI
     * 
     * @param in
     *            the InputStreamReader containing the JS
     * @param out
     *            the Writer Object
     * @throws EvaluatorException
     * @throws IOException
     */
    public static void compress(final InputStreamReader in, Writer out)
            throws EvaluatorException, IOException {
        JavaScriptCompressor compressor = new JavaScriptCompressor(in,
                new ErrorReporter() {
                    public void warning(String message, String sourceName,
                            int line, String lineSource, int lineOffset) {

                        System.out.println("\n[WARNING]");
                        if (line < 0) {
                            System.out.println(" " + message);
                        } else {
                            System.out.println(" " + line + ':' + lineOffset
                                    + ':' + message);
                        }
                    }

                    public void error(String message, String sourceName,
                            int line, String lineSource, int lineOffset) {
                        System.out.println("[ERROR] ");
                        if (line < 0) {
                            System.out.println(" " + message);
                        } else {
                            System.out.println(" " + line + ':' + lineOffset
                                    + ':' + message);
                        }
                    }

                    public EvaluatorException runtimeError(String message,
                            String sourceName, int line, String lineSource,
                            int lineOffset) {
                        error(message, sourceName, line, lineSource, lineOffset);
                        return new EvaluatorException(message);
                    }
                });

        // call YUI
        compressor.compress(out, 0, true, false, false, false);
    }
}

For testing purposes I imported an uncompressed version of jQuery and created a file named helloWorld.js. The helloWorld.js contains a single function only, just to test and verify the outcome of the servlet.

Then, I created a test.html, a simple HTML page which loads the mimimized JavaScript files:

<html>
 <body>
   <h1>Test</h1>
   <script src="./xsp/minimizer/?files=jquery-1.11.1.js+helloWorld.js">
   </script>
 </body>
</html>

This is how the WebContent folder looks like in package explorer:

When opening the test.html page, you can see that only a single request is made to load the data from the servlet:

The size of the response is only 30 % of the original jQuery source request (which is GZIPed too):

When testing the code in the Firebug console, everything is working as expected. The “HelloWorld” function shows an alert…

and jQuery is defined:

The performance on my test server is very good, without code optimization or caching of the generated JavaScript elements.

Veröffentlicht unter Java, Java Script, Web, XPages | Verschlagwortet mit , , , , , | 6 Kommentare

XPages: WebContent Files (2) – Manipulate exitsting files using the Java NAPI

In this article, I will shortly give an overview how you can edit existing file from the WebContent folder (Don’t miss the first article on this topic).

First, let’s create a view to display the design elements of the WebContent folder. To do this, I have an old school LotusScript Agent which updates the selection formula of a view (Some details about this technique can be found here).

Sub Initialize

    Dim session As New NotesSession
    Dim doc As NotesDocument
    Dim db As NotesDatabase
    Dim view As NotesView
      
    Set db = session.Currentdatabase
    Set view = db.Getview("DesignView")
    
    view.SelectionFormula = |@Contains($FlagsExt; "w")|
    
    Set doc = db.GetDocumentByUNID(view.UniversalID)
    Delete view
    
    doc.ReplaceItemValue "$FormulaClass", "7FFF"
    doc.Sign
    doc.Save True, False

End Sub

The agent has to run once to change the view’s selection criteria. In this example the view has the name “DesignView”. After that, we can add a single column to the view to display the files and their names:

Now lets build a simple XPage named Files.xsp to select the file you want to edit:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:viewPanel
        rows="30"
        id="viewPanel1"
        var="rowEntry">
        <xp:this.facets>
            <xp:pager
                partialRefresh="true"
                layout="Previous Group Next"
                xp:key="headerPager"
                id="pager1">
            </xp:pager>
        </xp:this.facets>
        <xp:this.data>
            <xp:dominoView
                var="viewDesign"
                viewName="DesignView">
            </xp:dominoView>
        </xp:this.data>
        <xp:viewColumn
            columnName="$FileNames"
            id="viewColumnFileNames"
            displayAs="link"
            >
            <xp:this.pageUrl>
                <![CDATA[#{javascript:"/Editor.xsp?filename=" +
                   rowEntry.getColumnValues().get(0)}]]>
        </xp:this.pageUrl>
        </xp:viewColumn>
    </xp:viewPanel>
    <xp:br />

</xp:view>

The Files.xsp lists all files as links and opens them via the Editor.xsp XPage:

This is the Editor.xsp:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

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

    <xp:inputTextarea
        id="inputTextarea1"
        value="#{fileBean.fileData}"
        rows="40"
        cols="80" />
    <xp:br />
    <xp:button
        value="Load"
        id="buttonLoad">
        <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:view>

It uses a simple managed bean…

package ch.hasselba.napi;

import java.io.Serializable;

public class FileDataBean implements Serializable {

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

    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);
    }
}

… which is initialized with the properties defined in the faces-config.xml. There you can find the database server and the database path:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <managed-bean>
    <managed-bean-name>fileBean</managed-bean-name>
    <managed-bean-class>ch.hasselba.napi.FileDataBean</managed-bean-class>
    <managed-bean-scope>view</managed-bean-scope>
    <managed-property>
      <property-name>dbPath</property-name>
      <value>NAPI.nsf</value>
    </managed-property>
    <managed-property>
      <property-name>dbServer</property-name>
      <value>DEV01/Hasselba/CH</value>
    </managed-property>
  </managed-bean>
</faces-config>

And last but not least, the required NAPIUtil class:

package ch.hasselba.napi;

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;

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) {
            e.printStackTrace();
        } 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) {
            e.printStackTrace();
        } 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) {
                }
            }
        }
    }
}

If the class looks like this…

…just hover one  of the entries and select “Fix project setup”.

Then, you can choose the missed bundle:

Build the project, and open one of the files by clicking a link in the Files.xsp. Here is an example for the file WEB-INF/faces-config.xml:

Now you can click the “Load” button to read the content of the file.

You can edit the file now and save it to the NSF.

If you got to package explorer (Hit F9 for refresh) you can see the changes:

Veröffentlicht unter Java, Server, XPages | Verschlagwortet mit , , , , , | Hinterlasse einen Kommentar

XPages: WebContent Files (1) – Create a file using the Java NAPI

The great Marky Roden has written an interesting article about using the WebContent folder instead of standard domino design elements. To create or manipulate these files programmatically, you can use the Java NAPI.

The first example demonstrates the creation of a file using a Java Agent. Before you can compile the code, you have to import the required jars as described here.

import lotus.domino.AgentBase;

import com.ibm.designer.domino.napi.NotesConstants;
import com.ibm.designer.domino.napi.NotesDatabase;
import com.ibm.designer.domino.napi.NotesNote;
import com.ibm.designer.domino.napi.NotesSession;
import com.ibm.designer.domino.napi.design.FileAccess;
import com.ibm.jvm.util.ByteArrayOutputStream;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

        try {
            final String serverName = "Dev01/Hasselba/CH";
            final String dbPath = "NAPI.nsf";
            final String fileName = "dummy.txt";

            NotesSession nSession = new NotesSession();
            NotesDatabase nDatabase = nSession.getDatabaseByPath(serverName
                    + "!!" + dbPath);
            nDatabase.open();

            // create a new note
            NotesNote nNote = nDatabase.createNote();

            // define $Flags item
            StringBuffer flags = new StringBuffer();
            flags.append(NotesConstants.DESIGN_FLAG_HIDEFROMDESIGNLIST);
            flags.append(NotesConstants.DESIGN_FLAG_NO_COMPOSE);
            flags.append(NotesConstants.DESIGN_FLAG_HIDE_FROM_V4);
            flags.append(NotesConstants.DESIGN_FLAG_FILE);
             
            // define $FlagsExt item
            StringBuffer extFlags = new StringBuffer();
            extFlags.append(NotesConstants.DESIGN_FLAGEXT_WEBCONTENTFILE);

            // init the file with the flags
            nNote.initAsFile(flags.toString(), extFlags.toString());

            // add required fields
            nNote.setItemText("$TITLE", fileName);
            nNote.setItemText("$MimeType", "text/plain");

           // generate some random data
           ByteArrayOutputStream bos = new ByteArrayOutputStream();
           for (int i = 0; i < 128000; i++) {
             int hlp = (i % 24) + 60;
             bos.write(hlp);
           }

           // store the data to the Note
           FileAccess.saveData(nNote, fileName, bos.toByteArray());

           // recycle the NAPI objects
           nNote.recycle();
           nDatabase.recycle();
           nSession.recycle();

       } catch (Exception e) {
           e.printStackTrace();
       }
    }
}

As you can see, when using the NAPI you don’t have to save a note. It is directly written to the NSF.

After running the agent and refreshing the project explorer view (hit F9) the newly created file appears in the WebContent folder…

… and is accessible in the browser:

If you want to allow public access to your element, you have to add a single line in the code above:

nNote.setItemText("$PublicAccess", "1");

This allows unauthenticated users to access the file while the rest of the application is protected:

On the other hand, you can use reader fields enhance the security of the file. But this can not be done with the note object directly, you have to use the standard Domino document classes for this. To access the document, you have to convert the note’s note id (an integer value) into his hexadecimal representation:

int noteId = nNote.getNoteId();
Document doc = db.getDocumentByID(Integer.toHexString(noteId));

Then, you can add the reader field to the document and save the result.

Session session = getAgentSession();
Database db = session.getCurrentDatabase();
Document doc = db.getDocumentByID(Integer.toHexString(noteId));

// create the field 
Item item = doc.replaceItemValue("DocReaders", "[ReadAll]");
item.setReaders(true);

// save the document
doc.save();

// recycle the instances
item.recycle();
doc.recycle();
db.recycle();
session.recycle();

Now, the file is protected when opening in the browser:

Keep in mind: As the developer it is required to have the access rights for the file too.

Veröffentlicht unter Agenten, Java, XPages | Verschlagwortet mit , , , , , | 7 Kommentare

XPages: Running Google’s Chrome V8 Javascript Engine

After answering a question on Stackoverflow.com about the Prototype problematic in the XPages SSJS engine, I thought of running another Javascript engine on top of Domino.

While you can use the JavaScripting API JSR223, I choosed the jav8 project for a test how this can be realized. So I downloaded the Windows binaries to get the required DLL and imported it into a new database. I also imported the source files of the lu.fler.script package to recompile all required classes.

Then, I registered the factory service by creating a javax.script.ScriptEngineFactory file in the /META-INF/services folder and added the line lu.flier.script.V8ScriptEngineFactory.

The package explorer looked like this:

To prevent collisions, I commented out some names in the V8ScriptEngineFactory class:

For a simple test, I decided to invoke the engine manually when clicking on a button on a XPage. To do this, I created a simple ActionListener in Java which loads the JavaScript Engine and evals a simple ” var i = 1+1″. The Javascript variable is then accessed and printed out to the server console.

package ch.hasselba.xpages.jav8;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JAV8Test implements javax.faces.event.ActionListener {

    static ScriptEngineManager factory = new ScriptEngineManager();
    static ScriptEngine engine = factory.getEngineByName("jav8");

    public void processAction(ActionEvent actionEvent)
            throws AbortProcessingException {
        try {
            System.out.println( engine.getClass().getCanonicalName() );
            engine.eval("var i=1+1;");
            System.out.println( "i = " + engine.get("i") );
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }
}

The XPage to test the ActionListener is rather simple too:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:button
        value="Click Me!"
        id="buttonClickMe">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.actionListeners>
                <xp:actionListener type="ch.hasselba.xpages.jav8.JAV8Test" />
            </xp:this.actionListeners>
        </xp:eventHandler>
    </xp:button>
    
</xp:view>

When the button is clicked, the V8 engine works as it should:

But now comes a hard problems: It works only once! After doing this, my test server crashes completly. During playing with it, I was able to run it as it should, but I have no idea anymore how I did it. I think it is the DLL and static classes, but I am currently to busy to investigate the problem further. The @Override notations added to the methods (which must removed before the code can be compiled) do not override exitisting ones (I checked the bundled javax.script JAR of the binary package), this does not seem to be the problem. Maybe someone else has an idea?

Veröffentlicht unter Allgemein, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Hinterlasse einen Kommentar