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 cmpresses 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 , , , , , | 1 Kommentar

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

GDL, Streik & der deutsche Michel (2)

Da ja der eine oder andere zürnt, der Streik würde tagtäglich “immense volkswirtschaftlichen Schäden anrichten”, sollten sich Betroffene vielleicht doch besser mal den Schaum vom Mund abwischen und die Zahlen in die passenden Relationen bringen:

Jeder Streiktag kostet geschätzte 50 bis 100 Millionen Euro, je nachdem, wer da so gefragt wird. Kostentechnisch entspricht das einem 50tel der aktuellen Kosten des Berliner Flughafens. Oder ein 68tel von Stuttgart 21. Die Liste lässt sich beliebig fortsetzen.

Jetzt, wo das Ende des Streiks für Samstag angekündigt ist, kann man sagen, dass der Streik nicht mal soviel gekostet hat wie das Euro Hawk-Debakel. Und um die Kosten der letzten Krise der WestLB zu toppen, müssten die Lokführer sogar mindestens ein halbes Jahr am Stück streiken, um den gleichen Schaden anzurichten..

Selbst das hat die Volkswirtschaft nicht in den Abgrund gestürzt. Und auch sonst war der Aufschrei des deutschen Michels recht klein.

Ach, und wenn der Schaum gerade weggewischt ist: Herr Weselsky lebt mit seiner “analog zur Besoldungsgruppe A16″ bezahlten Tätigkeit nicht gerade in Saus und Braus oder führt gar ein Luxusleben. Hingegen nagt der Vorstand der DB alles andere als am Hungertuch. Letzteres scheint nur irgendwie nicht weiter erwähnenswert zu sein…

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

GDL, Streik & der deutsche Michel

Wenn ich mir die Kommentare in den unterschiedlichsten Foren rund um das Thema “GDL & Streik” durchlese, frage ich mich ernsthaft, in was für einem Land ich eigentlich lebe: Man ist ja viel gewohnt von den “Berufstrollen“, die sich den ganzen Tag den Lebensfrust von der Seele schreiben. Und man findet natürlich auch die eher zur Erheiterung beitragenden Formulierungen, wie zum Beispiel den, dass man “wegen dem Sch…ß Ossi nicht zu den Feierlichkeiten des Mauerfalls nach Berlin reisen könne” (Der Kommentar ist nur Sinngemäß wiedergegeben, er war deutlich länger, und man kann auf Ironie-Freiheit schließen).

Doch was da sonst so abgesondert wird, ist eher beschämend, wie z.B. der hier:

................................................................
................................................................
................................................................
................................................................
................................................................
................................................................

Zitat “Dark Agenda” und dessen Vorredner “BMG Fan”
(Aus Gründen des Leistungsschutzrechtes hier nicht wiedergegeben)

Auch die Ergebnisse aus den unterschiedlichsten Umfragen (wenn auch nicht repräsentativ) sprechen eine deutliche Sprache: Weselsky sei Größenwahnsinnig, der Streik unberechtigt, und das gehe ja gar nicht. Die Medien dreschen auf Weselsky ein, und die Stimmen aus der Politik, die sich ja eigentlich gar nicht in dieses Thema involvieren dürfen, sind auch nicht gerade freundlich. Obwohl letzteres nur bedingt überrascht, den dass die SPD ihr eigenes Klientel schon länger verraten hat, ist ja kein Geheimnis (wenn man annimmt, das Klientel sei der Arbeitnehmer).

Mal abgesehen davon, das 91 Prozent der GDL Mitglieder für einen Streik sind, und es daher schon befremdlich ist, Herrn Weselsky vorzuwerfen, die Interessen seiner Mitglieder auch wahrzunehmen (Ok, das ist wirklich etwas ungewöhnlich für den deutschen Michel: Man wählt, und bekommt auch dass, was man gewählt hat), muss man sich auch mal vor Augen halten, dass hier ein Grundrecht wahrgenommen wird. Es gab Zeiten, da hatten die Lokführer dieses Grundrecht nicht, da waren Sie noch verbeamtet. Aber dann hat man Ihnen aus Kostengründen das Streikrecht gegeben, und im Gegenzug das eine oder andere Recht genommen und gewisse Ansprüche beschnitten (ohne Kostenausgleich versteht sich).

Es ist schon erstaunlich, wie unsolidarisch die Menschen in diesem Land sind, wenn es sie direkt betrifft. Streik ja, aber bitte nur so, dass es keinen stört. Und wenn man dann noch den Zuspruch für die von Angela* Andrea Nahles geplante Beschneidung des Streikrechts liest und hört, kann einem  nur noch die Kinnlade runterklappen: Da wird also ernsthaft geplant, kurz- wie auch mittelfristig das Streikrecht auszuhebeln, denn bis die Frage geklärt ist, wer denn überhaupt die “Gewerkschaftliche Zuständigkeit” hat (und das kann ja schonmal eine Weile dauern), kann ein Streik kann richterlich untersagt werden. Und die Bevölkerung findet das gut? Wünschen sich die Leute in diesem Land Verhältnisse wie unter Maggie Thatcher?

Wieso kann man nicht mit mehreren Gewerkschaften verhandeln? “Preferred Supplier”, ich verstehe…

Ich jedenfalls stehe zu 100% hinter den Lokführern. Den dieses Land braucht starke Gewerkschaften, um dem täglichen Druck aus Politik und Wirtschaft etwas entgegen zu setzen. Auch wenn ich dafür mal eine Weile länger im Stau stehe, also sonst…

Nachtrag:

Die Argumentationschiene, dass es verwerflich sei, wenn eine Berufsgruppe eine ganze Volkswirtschaft in Geiselhaft nehmen würde und einen Millionenschaden anrichtet, finde ich übrigens Klasse (Beispielhafter Kommentar)! Gegen solche Leute muss nämlich vorgegangen werden, da stimme ich zu 100% zu. Aber was hat das mit der GDL und den Lokführern zu tun?

*: Freud’scher Vertipper
Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

Eine neue Zwiebelschicht

Es sind manchmal die kleinen Dinge, die einen auf die großen Probleme aufmerksam machen: Eine kleine, zusätzliche Bitte im “Projekt-Anbahnungsgespräch” zum Beispiel. Nichts wildes – nur eine freiwillige Angabe, die aber – wenn sie denn Schulung macht – in meinen Augen für alle IT Freiberufler in Deutschland dramatische Auswirkungen haben wird.

Was war geschehen?

Am gestrigen Tag habe ich eine Projektanfrage erhalten, die ich – wie immer – gerne beantwortet habe, mit allen relevanten Angaben wie Stundensatz, Verfügbarkeit und natürlich meinem aktuellen Profil. Es gibt nur ein paar unseriöse Firmen am Markt, bei denen es generell keine Reaktion von mir gibt – es sei denn, der angebotene Stundensatz ist derartig lächerlich (Yesterday’s Highlight: max. 40 €/Stunde, München), da schreib ich dann auch mal ein paar Zeilen zu und werde auch etwas pampig.

Das auf die Antwort zu einer Anfrage reagiert wird, ist in diesem Business leider zu einer Seltenheit geworden (obwohl der Projektanbieter den Erstkontakt hergestellt hat), umso erfreulicher war die prompte Reaktion des Anfragenden. Allerdings war ein kleines Formular beigefügt, das man doch bitte ausfüllen soll: Erst einmal die Frage, ob man denn freiberuflich tätig wäre oder doch lieber als Gesellschaft firmiert. Und ob man einen Nachweis hat, freiberuflich anerkannt zu sein. Oder schonmal von der Rentenversicherungsanstalt ein Überprüfungsverfahren hinter sich gebracht hat. Alle Angaben freiwillig, versteht sich.

Und das war der Moment, wo ich etwas stutzig wurde, und zum Telefonhörer griff: Man hätte da Probleme als Personalvermittler bekommen, und horrende Summen an die Rentenversicherungsanstalt nachzahlen dürfen, so der Mann am Telefon. Und man wolle da das Risiko im Vorfeld abklären, nicht das da eine Prüfung wegen Scheinselbständigkeit erfolgen würde. Wenn ich natürlich als Firma auftreten würde, und nicht als Freiberufler, würde sich das positiv in der Kandidatenauswahl für das Projekt auswirken…

Mal abgesehen von dem Umstand, dass ich darauf vorbereitet bin, und jederzeit meine Projekte durch eine Firma “durchschleusen” kann, um so dem Auftraggeber das Problem mit der Scheinselbständigkeit zu lösen, warum sollte ich das für eine Vermittlerfirma tun?

Nur mal kurz als Hintergrundinformation:

Scheinselbständigkeit ist, wer – grob formuliert – die Arbeit eines Festangestellten verrichtet, und wirtschaftlich vom Auftraggeber abhängig ist. Verhindert werden soll, dass Unternehmen ihre Mitarbeiter rauskegeln, als Selbständige wieder anstellen und so das Arbeitrecht aushebeln. Und natürlich sollen auch Sozialbeiträge erhalten bleiben. Soweit, so gut.

Doch die rechtliche Situation ist so unglaublich schwammig, denn es gibt hierbei nur Indizien, die entweder die Scheinselbständigkeit untermauern, oder eben das Gegenteil aufzeigen. Aber eben alles nur Indizien, und daher muss alles im Einzelfall geprüft werden. Eine Anerkennung als Freiberufler durch das Finanzamt hilft hier rechtlich nicht.

Rechtssicherheit sieht anders aus.

Daher sind Freiberufler wie ich generell Freiwild für Ansprüche wegen “angeblicher Scheinselbständigkeit”, und die erstrecken sich dabei auch auf Nachforderungen wegen nicht geleisteter Sozialabgaben. Und die muss dann auch der Auftraggeber bezahlen, also die Vermittlerfirma.

Nur als Beispiel: Wenn ich ein Haus baue, dann plant mir ein Architekt das Vorhaben. Es ist klar das seine Beschäftigung spätestens mit Abschluss des Bauvorhabens beendet ist, und auch während des Hausbaus ist es mir als Auftraggeber relativ egal, wieviele Kunden der Architekt neben mir so hat. Er ist also nicht an mich gebunden, und ich kann ihm keine Weisungen erteilen, wie er seinen Arbeitstag zu strukturieren oder wo er seine Arbeit zu verrichten hat. Es kann sein, dass der Bau meiner (leider fiktiven) Luxusvilla so unglaublich aufwendig ist, dass er – dank meines Auftrages – eine ganze Weile voll durch mich in Beschlag genommen wird, und daher jeden Monat seinen Rechnungen nur an mich schickt. Für den Moment ist er natürlich finanziell von mir abhängig, und auch auf dem Papier bin ich sein einziger Geldgeber. Doch ist er deswegen von mir fest angestellt, mit allen Rechten und Verpflichtungen? Natürlich nicht.

Gerade im Projektgeschäft, wo man durchaus für ein halbes oder ganzes Jahr in einem Kundenprojekt Vollzeit gebunden ist, macht es die Sache rechtlich sehr spannend, denn auf einmal ist man wirtschaftlich Abhängig von einem Auftraggeber und dadurch ein Kandidat für die Rentenversicherungsanstalt und deren Rechtsabteilung. Mittlerweile ist die Situation so absurd geworden, dass sogar Projekte im einzelnen bewertet werden: Das eine Projekt war freiberuflich, und das andere, ja da war man wohl in einem Angestelltenverhältnis.

Diese rechtliche Unsicherheit wurde bis heute nicht befriedigend gelöst, und daher haben Großunternehmen darauf reagiert und beschäftigen externe Fachkräfte nicht mehr direkt. Das Problem wurde auf diese Weise outgesourced, und nebenbei erspart sich das Großunternehmen den unnötigen administrativen Wasserkopf für die unterschiedlichen Verträge und Abrechnungsmodelle der einzelnen beschäftigten Externen. Den lukrativen Kanal zum Auftraggeber bezahlen die “Preferred Supplier” (aka Projektvermittler) sogar und überweisen den einen oder anderen Euro an das Großunternehmen (für Zertifizierungen und ähnliches), den sie später durch die Vermittlung natürlich wieder reinbekommen. Aus Sicht des Großunternehmens also eine nachvollziehbare Lösung, erhöht sie doch nebenbei den Shareholder Value. Und für den Projektvermittler natürlich eine herrliche Situation, mit Hilfe der Türsteherfunktion Druck auf die externen Ausüben zu können: Du kommst hier nicht rein. Nicht mit diesen Schuhen.

Die bisherige Schieflage soll jetzt wohl nocheinmal verstärkt werden: Bisher war es der Projektvermittler, der das “Scheinselbständigkeitsproblem” übertragen bekommen hat, und letztlich stellt es dessen Geschäftsmodell dar. Und nun wird versucht, das Problem an die Externen zu übertragen, aber natürlich ohne Gegenleistung. Als kleiner Nebeneffekt werden auch noch Haftungsrisiken durchgereicht, ohne das dies näher auffällt.

Gestern war es noch eine freiwillige Angabe. Aber ich befürchte, das es Praxis wird, denn irgendeiner wird das Spiel schon mitmachen. Und dann wird es schnell zur Standardbedingung.

Es beunruhigt mich, denn der Gesetzgeber hat hier ein Problem geschaffen, ohne eine Lösung anzubieten. Die Preise für externe Kräfte werden weiter in die Höhe getrieben, ohne dass der Auftraggeber einen Nutzen davon trägt. Und der Auftragnehmer erhält weniger, denn die einzelnen Zwiebelschichten der “Zwischenfirmen” müssen ja auch irgendwie finanziert werden. Und der Sinn des Ganzen erschießt sich mir auch nicht, ausser dass man die freiberufliche Dienstleistung im IT Sektor gesellschaftlich abschaffen möchte.

Ach, ein Merkmal des eingangs erwähnten Projektes ist übrigens, dass die “Einsäte nur sporadisch stattfinden”. Also ist es von vorneherein klar, dass hier keine Probleme mit der Rentenkasse entstehen können. Doch nachdem ich dem Projektanbieter mitgeteilt habe, dass ich in diesem Projekt nur als Freiberufler agieren werde, kam die prompte Antwort, dass man leider nur Kapitalgesellschaften akzeptieren und mich daher leider nicht berücksichtigen könne. Denn den “sogenannten Freelancer-Check” würde ich nicht bestehen.

Ich sehe rosige Zeiten auf die Branche zukommen…

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

Krautreporter sind online

Das Warten hat ein Ende! Mit dem heutigen Tag ist das Krautreporter-Projekt endlich online gegangen. Ich bin gespannt, was sich aus diesem Versuch alles entwickelt.

Es ist erfrischend, das man beim Besuch der Seite nur auf einen einzigen Tracker stößt, und nicht wie bei manchen anderen Online-Magazinen die Ghostery-Warnungen die halbe Seite blockiert.

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

MongoDB for Java Developers

Today I received the confirmation for successfully completing the “MongoDB for Java Developers” course:

Here is the link to verify the certificate.

Veröffentlicht unter MongoDB | Verschlagwortet mit | Hinterlasse einen Kommentar

XPages: Execute Events with HTTP Get

To execute an event on the server, you normally have to send a POST request, because actions will be executed in the Invoke Application phase of the JSF lifecycle. A GET request will only process the Restore View and the Render Response phase, that why you can not execute an event with a GET request.

But with the help of a PhaseListener, the execution can be done earlier in the Restore View phase:

package ch.hasselba.xpages.util;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import com.ibm.xsp.component.xp.XspEventHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import com.ibm.xsp.util.FacesUtil;

public class ExecuteOnServerPhaseListener implements PhaseListener {

    private static final long serialVersionUID = 1L;

    public void beforePhase(PhaseEvent event) {}

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    public void afterPhase(PhaseEvent event) {
        
        FacesContextExImpl = FacesContextExImpl.getCurrentInstance();
        ExternalContext ec = fc.getExternalContext();
        String url = ec.getRequestPathInfo();
        String[] pathes = url.split( "/" );
        
        try{
            if( pathes.length > 2 ){
                if( "executeOnServer".equals( pathes[pathes.length -2 ] ) ){
                    String[] fullId = pathes[ pathes.length - 1 ].split(":");
                    String actionId = fullId[ fullId.length - 1 ];
                    XspEventHandler eventHandler = (XspEventHandler)
                        FacesUtil.findComponentWithFullId( fc.getViewRoot(), actionId );
                    if( eventHandler != null ){
                        eventHandler.getAction().invoke( fc, null );
                        fc.responseComplete();
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        
    }

}

To activate the PhaseListener, it has to be enabled in the faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
   <lifecycle>
      <phase-listener>ch.hasselba.xpages.util.ExecuteOnServerPhaseListener</phase-listener>
   </lifecycle>
</faces-config>

The following Javascript snippet extends the XSP object and adds the new function executeOnServerGet to it. The parameter is the if of the event to invoke.

XSP.executeOnServerGet = function( eventId ){
    var viewId = dojo.query('[name="$$viewid"]')[0].value;
    var url = document.forms[0].action;
    url += "/executeOnServer/" + eventId;
    url += "?$$viewid=" + viewId;
    url += "&$$ajaxid=@none";
    dojo.xhrGet({url: url});
}

When calling the function, it sends a GET request and adds the current view id to the request. With the parameter $$ajaxId set to @none, the XPages Engine is forced to send no HTML code back to the client.

And here is an example XPage:

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

    <xp:eventHandler id="helloworld" event="helloworld" submit="false">
        <xp:this.action>
            <![CDATA[#{javascript:
               print("hello world " + java.lang.System.currentTimeMillis() );
            }]]>
        </xp:this.action>
    </xp:eventHandler>


    <xp:scriptBlock id="scriptBlock1">
        <xp:this.value><![CDATA[
            dojo.addOnLoad( function(){
                XSP.executeOnServerGet = function( eventId ){
                      var viewId = dojo.query('[name="$$viewid"]')[0].value;
                    var url = document.forms[0].action;
                    url += "/executeOnServer/" + eventId;
                    url += "?$$viewid=" + viewId;
                    url += "&$$ajaxid=@none";
                    dojo.xhrGet({url: url});
                  }
            });
        ]]></xp:this.value>
    </xp:scriptBlock>
    
    <xp:button value="Execute" id="button1">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script>
                <![CDATA[XSP.executeOnServerGet( "#{id:helloworld}" );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
</xp:view>

When clicking the button, the following URL is opened in the background:

http://example.com/db.nsf/EventGet.xsp/executeOnServer/view:_id1:helloworld?$$viewid=!dwjldz64w0!&$$ajaxid=@none

A GET request was sent to the server:If you look on the server console, you will see that the Action was invoked:

Veröffentlicht unter Dojo Toolkit, Java, Java Script, JSF, Performance, XPages, XSP | Verschlagwortet mit , , , , , , , , , , | 2 Kommentare