XPages: Use a Method Binding as Converter

I accidentally found a way to add a method binding as a converter to a component, because I have added a managed bean as a converter directly in the source code. The DDE does not support this.

If you go to the converter property of a component, you can only add one of the predefined converters:

But you can go to the source and add a method binding to the option, in this case my bean which implements my converter functionality.

<xp:inputText
    id="inputText1"
    value="#{sessionScope.inputText1}"
    converter="#{myConverterBean}" />

If you now reopen the saved XPage, the converter property is filled in, but cannot edited / changed anymore.

You must remove the property in the source code to get the old behaviour back.

Tested in 8.5.2, 8.5.3 & ND 9

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , , , , | 5 Kommentare

XPages: Optimized Partial Refreshs (2)

With the Optimized Partial Refresh you can do a lot of nice things: If only a part of the form is sent to the server, only this part of the components in the JSF component tree will be processed. This means that only submitted values are applied, coverted and validated, which can result in less server usage and a better performance.

Normally, if you have two required fields on you XPage and do a Partial Refresh, both fields will have a validation error:

But with this technique, you can choose which component should be refreshed. When clicking of Button Refresh 1, only the first field is marked as a required field:

When filling in the first field, but clicking on Refresh 2

… the value of the first field gets lost, because it was not sent to the server (if a value was already stored there, it won’t get lost). And the second field is marked as required:

This is the example XPage, the CSJS code is moved to the Custom Control ccOptimizedPR.

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

    <xc:ccOptimizedPR />

    <xp:div id="refreshMe">
        <xp:messages id="messagesError" />
        <xp:inputText
            id="inputText1"
            value="#{sessionScope.inputText1}">
            <xp:this.validators>
                <xp:validateRequired message="Field 1 is empty!" />
            </xp:this.validators>
        </xp:inputText>
        <xp:inputText
            id="inputText2"
            value="#{sessionScope.inputText2}">
            <xp:this.validators>
                <xp:validateRequired message="Field 2 is empty!" />
            </xp:this.validators>
        </xp:inputText>
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial"
            refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

    <xp:button
        value="Refresh 1"
        id="buttonCleaned1">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText1}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
    <xp:button
        value="Refresh 2"
        id="buttonCleaned2">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText2}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

</xp:view>
Veröffentlicht unter Dojo Toolkit, Java, JSF, Performance, XPages, XSP | Verschlagwortet mit , , , , , , , , , | Schreib einen Kommentar

XPages: Optimized Partial Refreshs

Inspired by the last post of Mark, I have created a small CSJS snippet which allows to optimize the behaviour of a Partial Refresh. Normally, if you execute a Partial Refresh, all elements of a form are sent to the server. Take a look at this XPage:

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

    <xp:inputText
        id="inputText01"
        value="#{sessionScope.inputText01}" />
    <xp:inputText
        id="inputText02"
        value="#{sessionScope.inputText02}" />
    <xp:inputText
        id="inputText03"
        value="#{sessionScope.inputText03}" />
    <xp:inputText
        id="inputText04"
        value="#{sessionScope.inputText04}" />
    <xp:inputText
        id="inputText05"
        value="#{sessionScope.inputText05}" />
    <xp:inputText
        id="inputText06"
        value="#{sessionScope.inputText06}" />
    <xp:inputText
        id="inputText07"
        value="#{sessionScope.inputText07}" />
    <xp:inputText
        id="inputText08"
        value="#{sessionScope.inputText08}" />
    <xp:inputText
        id="inputText09"
        value="#{sessionScope.inputText09}" />
    <xp:inputText
        id="inputText10"
        value="#{sessionScope.inputText10}" >
    </xp:inputText>

    <xp:div id="refreshMe">
        <xp:label
            value="#{javascript:java.lang.System.currentTimeMillis()}"
            id="labelNow" />
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial" refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

</xp:view>

The button refreshes only a small portion of the XPage, the DIV element refreshMe. It does not require any of the posted field values, it just refreshes the DOM element in the frontend. But when clicking the button, the posted data to the server contain all the fields and their values, which is not necessary in this case.

This is how the request looks like in Firebug (I have prefilled all fields with the values 1..9):

The response of the server is – as expected – the actual HTML code for the refreshed DOM element:

The optimized  version adds the option clearForm to partial refreshs. When using this option, only the XPages internal fields are sent to the server, but DOM will be refreshed correctly:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

Now, the POST looks like this:

You may think „This is useless, you can do a XSP.partialRefreshGet instead!“

That’s why there is the second option additionalFields. This allows to define all fields you want to update during the refresh:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText01}',
                                              '#{id:inputText02}' ],
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

When clicking the button now, the specified fields are added to the POST request (and will be updated in the JSF tree:

Here is the snippet (Tested on IE 11, Chrome 33 & FF 27 with ND9 & ND 8.5.3 )

<xp:scriptBlock id="scriptBlockPROptimized">
        <xp:this.value><![CDATA[
XSP.addOnLoad(function(){

    // hijack the existing partial refresh method
    if( !XSP.__partialRefresh ){
        XSP.__partialRefresh = XSP._partialRefresh;
    }

    // add the new one to the XSP object
    XSP._partialRefresh = function x_prfh(method, form, refreshId, options){

        // clear the form?
        if( options.clearForm ){

            // create a new HTML form...
            var newForm = document.createElement( "form" );
            newForm.setAttribute( "method", form.method );
            newForm.setAttribute( "action", form.action );

            // ... and loop all existing fields
            for( var i = 0; i<form.length; i++ ){
                var field = form[i];
                var fieldName = field.name;
                var includeField = false;

                try{

                    // check for addition fields
                    if( options.additionalFields ){
                        includeField = dojo.indexOf(options.additionalFields, fieldName)!=(-1)?true:false;
                    }

                    // only add XPages relevant fields and addtional fields
                    if( fieldName == form.id || fieldName.substr(0,2) == '$$' || includeField ){

                        var newField = null;
                        if( field.options ){
                            // special handling for fields with options
                            for( var j=0; j<field.length; j++ ){
                                if( field.options[j].selected ){
                                    newField = document.createElement( "input" );
                                    newField.setAttribute( "type", "hidden" );
                                    newField.setAttribute( "name", fieldName );
                                    newField.setAttribute( "value", field.options[j].value );
                                    newForm.appendChild( newField );
                                }
                            }
                        }else{
                            // default field handling: just clone the DOM element
                            newField = field.cloneNode( true );
                            newForm.appendChild( newField );
                        }
                    }
                }catch(e){
                    console.log(e);
                }
            }

            // call the original refresh method with the new form
            return XSP.__partialRefresh(method, newForm, refreshId, options);
        }

        XSP.__partialRefresh(method, form, refreshId, options);
    };
});]]></xp:this.value>
    </xp:scriptBlock>

Just add the script block above to your XPage, or move it into a CSJS library. But keep in mind that you have to think twice when removing data from the request. It can lead in an inconsistence between the data in the client and the data stored in the component tree on the server.

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

XPages: SSJS & How you can have fun at the office

This article is a demonstration of what harmful things you can do when using the SSJS & prototyping wrong (described here by the great Mark Roden). Don’t do that! Especially not on a productive server!
 

Wanna have a lot of fun in the office with the other developers? Just overwrite some global SSJS functionality! They will never find out what happend to their applications!

 

This is the application we destroy

Our demonstration application uses a small Java class which has only a single method:

package ch.hasselba.xpages;

public class Demo {

    public String foo() {
       return "bar";
    }
 }

This class is used for a label on our XPage and is loaded with the importPackage functionality of SSJS:

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

    <xp:label
        id="labelDemo">
        <xp:this.value>    
            <![CDATA[#{javascript:
                importPackage( ch.hasselba.xpages );
                return new ch.hasselba.xpages.Demo().foo();}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

When opening the XPage, nothing special happens, the label is filled with the value „bar“:

Now let’s destroy it!

Add a button on the XPage, containing a single line of SSJS code…

<xp:button
        value="Click Me"
        id="button1">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:prototype.importPackage = null;}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

… and reopen the XPage:

Question: What will happen if you click the button?

Answer:

Exactly: The method is destroyed on the whole server! Every XPage which imports a Java package with SSJS will not work anymore. Only a complete server restart helps now. The error message you will receive won’t help you, because it looks like the Java classes could not be found. Now „help“ the other developers with tips like „Maybe a build problem? Have you tried to clean the application?“.

P.S. You can execute the code on other ways, f.e, with an eval statement, or a method binding of your choice.

Tested on ND 8.5.3 & ND 9.0

Veröffentlicht unter ServerSide JavaScript | Verschlagwortet mit , , , , , | 5 Kommentare

XPages: Events, ActionListeners & Parameters

To access the event parameters of an event within your actionListener, you have to access the source object of your actionEvent object:

package ch.hasselba.xpages;

import java.util.List;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import com.ibm.xsp.complex.Parameter;
import com.ibm.xsp.component.xp.XspEventHandler;

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

    public void processAction(ActionEvent event)
            throws AbortProcessingException {
        XspEventHandler eventHandler = (XspEventHandler) event.getSource();
        List<Parameter> params = eventHandler.getParameters();
        for (Parameter p : params) {
            System.out.println(p.getName() + " -> " + p.getValue());
        }
    }
}

Here is an example XPage:

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

    <xp:button
        value="Label"
            id="buttonAction">
        <xp:eventHandler
            event="onclick"
            submit="true"
            immediate="true" refreshMode="norefresh">
            <xp:this.parameters>
                <xp:parameter
                    name="param"
                    value="#{javascript:java.lang.System.currentTimeMillis()}">
                </xp:parameter>
            </xp:this.parameters>

                <xp:this.actionListeners>
                    <xp:actionListener type="ch.hasselba.xpages.MyActionListener" />
                </xp:this.actionListeners>
            </xp:eventHandler>

    </xp:button>

</xp:view>

You can see the result on the console when the button is clicked:

When doing the same for an action, you have to access the UIComponent from the actionEvent:

package ch.hasselba.xpages;
import java.util.List;
import javax.faces.event.ActionEvent;
import com.ibm.xsp.complex.Parameter;
import com.ibm.xsp.component.xp.XspEventHandler;

public class MyActionListener {
public void execute(ActionEvent event) {
        XspEventHandler eventHandler = (XspEventHandler) event.getComponent();
        List<Parameter> params = eventHandler.getParameters();
        for (Parameter p : params) {
            System.out.println(p.getName() + " -> " + p.getValue());
        }
     }
}

Here is an example XPage, the method execute is contained by the managed bean myAction:

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

    <xp:button
        value="Label"
            id="buttonAction">
        <xp:eventHandler
            event="onclick"
            submit="true"
            immediate="true"
            refreshMode="complete"
            actionListener="#{myAction.execute}">
            <xp:this.parameters>
                <xp:parameter
                    name="param"
                    value="#{javascript:java.lang.System.currentTimeMillis()}">
                </xp:parameter>
            </xp:this.parameters>
        
        </xp:eventHandler>
            
    </xp:button>
    
</xp:view>
Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , , | Ein Kommentar

Quick-n-Dirty: Disable Validation for FileDownload control (2)

Because my old snippet does not work anymore for ND9 (the IBM changed the internal methods / objects) I had to create a new way to disable the validation of the FileDownload control. Now I have switched to a PhaseListener which waits for to any XspEventHandler and checks if this event handler is a child of the FileDownload control. If so, it skips the validation.

Here is the code for the PhaseListener:

package ch.hasselba.xpages;

import java.util.Iterator;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.component.xp.XspEventHandler;
import com.ibm.xsp.component.xp.XspFileDownload;
import com.ibm.xsp.context.FacesContextExImpl;
import com.ibm.xsp.util.FacesUtil;

public class FileDownloadPhaseListener implements PhaseListener {

    /**
     * FileDownloadPhaseListener
     * 
     * by Sven Hasselbach, 19.12.2014
     */
    private static final long serialVersionUID = 1L;

    public void afterPhase(PhaseEvent arg0) {}

    public void beforePhase(PhaseEvent arg0) {
        try{
            FacesContextExImpl fc = (FacesContextExImpl) FacesContext.getCurrentInstance();

            // get client id
            String clientId = FacesUtil.getHiddenFieldValue(fc); 

            // extract last id 
            String[] ids = clientId.split(":");
            String id = ids[ ids.length  - 1 ];

            // search for the component
            UIViewRootEx uiRoot = (UIViewRootEx) fc.getViewRoot();
            UIComponent cmp = FacesUtil.getComponentFor(uiRoot,  id );

            if( cmp instanceof XspEventHandler ){

                while( cmp != null ){

                    // found the download control?
                    if( cmp instanceof XspFileDownload ){
                        // disable validation & quit the process
                        fc.setDisableValidators( true );
                        break;
                    }
                    // climb up the component tree    
                    cmp = cmp.getParent();
                }
            }

        }catch(Exception e){}
    }

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

When using it on a XPage…

… you can now delete a File …

… but cannot submit a form with the required field:

This is the required faces-config.xml:

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

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , | Schreib einen Kommentar

Quick-n-Dirty: Use „Old-School“ Javacode in your XPages-Application

Because I often have read that it is not possible to access „Old School“ Java elements in XPages, but never understood the reason why, I have written my own class loader for fun to demonstrate how to do this:

1. I have created a simple Java library:

2. The library contains a single class „LoadedClass„:

The class is very simple. When it is instantiated, it just prompts the current timestamp to the server console:

package ch.hasselba.test;

public class LoadedClass { 

    public LoadedClass(){
        System.out.println("Time: " + System.currentTimeMillis() );
    }
}

3. If you inspecting the Java design element, you will notice a attachment with the name %%object.jar%%. That’s the compiled library:

And here is the UNID of the design element for accessing it

4. Now it is time for the class loader (the modified code originally came from Laurentiu Cristofor):

package ch.hasselba.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 
 * This class implements a simple class loader that can be used to load at
 * runtime classes contained in a JAR file.
 * 
 * (P)2000 Laurentiu Cristofor
 * 
 * Modified 2014 by Sven Hasselbach
 * 
 */
public class JarClassLoader extends ClassLoader {
    private Hashtable jarContents;

    /**
     * Creates a new JarClassLoader that will allow the loading of classes
     * stored in a jar file.
     * 
     * @param bos
     *            ByteArrayOutputStream containing a Jar
     * @exception IOException
     *                an error happened while reading the contents of the jar
     *                file
     */
    public JarClassLoader(ByteArrayOutputStream bos) throws IOException {

        // second get contents of the jar file and place each
        // entry in a hashtable for later use
        jarContents = new Hashtable();

        JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bos
                .toByteArray()));

        JarEntry je;
        while ((je = jis.getNextJarEntry()) != null) {
            // make sure we have only slashes, we prefer unix, not windows
            String name = je.getName().replace('\\', '/');

            // get entry size from the entry or from our hashtable
            int size = bos.size();
            // read the entry
            byte[] ba = new byte[size];
            int bytes_read = 0;
            while (bytes_read != size) {
                int r = jis.read(ba, bytes_read, size - bytes_read);
                if (r < 0)
                    break;
                bytes_read += r;
            }

            jarContents.put(name, ba);
        }

        jis.close();
    }

    /**
     * Looks among the contents of the jar file (cached in memory) and tries to
     * find and define a class, given its name.
     * 
     * @param className
     *            the name of the class
     * @return a Class object representing our class
     * @exception ClassNotFoundException
     *                the jar file did not contain a class named
     *                <code>className</code>
     */
    public Class findClass(String className) throws ClassNotFoundException {
        // transform the name to a path
        String classPath = className.replace('.', '/') + ".class";

        byte[] classBytes = (byte[]) jarContents.get(classPath);

        if (classBytes == null)
            throw new ClassNotFoundException();

        return defineClass(className, classBytes, 0, classBytes.length);
    }
}

The trick is to ignore the size of the JarEntries and use the size of the whole ByteArrayOutputStream.

5. Now we need to load the design element, extract the Jar and load our test class:

package ch.hasselba.demo;

import ch.hasselba.demo.JarClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.EmbeddedObject;
import lotus.domino.Session;

public class JavaLoader {

    public JavaLoader(Session session) {
        try {

            Database db = session.getCurrentDatabase();

            // get the design document
            Document doc = db
                    .getDocumentByUNID("6783F550459C81B1C1257C7E007A5D44");

            // get the attachment
            EmbeddedObject embObj = doc.getAttachment("%%object%%.jar");
            FileInputStream fis = (FileInputStream) embObj.getInputStream();

            // convert the FileInputStream to a ByteArrayOutputStream
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            int data;
            while ((data = fis.read()) != (-1)) {
                bos.write(data);
            }
            bos.flush();

            // load the Jar with the class loader
            JarClassLoader jcl = new JarClassLoader(bos);

            // get the class
            Class loadedClass = jcl.findClass("ch.hasselba.test.LoadedClass");

            // and instantiate it
            Object obj = loadedClass.newInstance();

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

6. Last but not least, a XPage for demonstrating how to use the code:

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

    <xp:button value="Label" id="button1">
        <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:
                    importPackage( ch.hasselba.demo );
                    new ch.hasselba.demo.JavaLoader(session);}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

</xp:view>

7. If you open the XPage and click the button…

… you can see the result on the server console:

If you want to access the methods and/or properties of the loaded object, you have do do this with reflection.

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

Veröffentlicht unter Java, XPages | Verschlagwortet mit , , , | Ein Kommentar

XPages: Modify the File Selection Button

With Dojo, you can easily customize the file upload button, f.e. to change the label, add additional style sheets or use HTML5 or Flash plugin for uploading.

Here is basic example which adds a label and a blue border around the button:

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

    <xp:this.data>
        <xp:dominoDocument var="document1" formName="File" />
    </xp:this.data>

    <xp:scriptBlock id="scriptBlockFileUpload">
        <xp:this.value>
            <![CDATA[
                require(['dojo/parser',
                         'dojox/form/Uploader',
                         'dojo/domReady!'], 
                         function(parser, ready){
                             parser.parse().then(function(){
                                uploader = new dojox.form.Uploader({
                                    name:'#{id:fileUpload1}', 
                                    label:'Select Some Files',
                                    multiple:false, 
                                    style: 'border:5px solid blue;'
                                }, '#{id:fileUpload1}');

                                uploader.startup();
                             });
                         });
            ]]>
        </xp:this.value>
    </xp:scriptBlock>

    <xp:fileUpload id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:button value="Submit" id="buttonSubmit" >
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete" immediate="false" save="true" />
    </xp:button>

</xp:view>

Have a look at dojox.form.Uploader for more details.

Veröffentlicht unter CSS, Dojo Toolkit, HTML, HTML5, Java Script, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Quick-n-Dirty: How to use the NAPI in a Java Agent

The undocumented NAPI for direct API calls in Java can easily used in normal agents. You just need to add two jar files to your java agent:

  • lwpd.commons.jar
  • lwpd.domino.napi.jar

These files can be found in <Notes>\osgi\shared\eclipse\plugins.

After importing, you do lot of funny things, f.e. opening a Session in Full Administration Mode, etc.

NotesSession nSession = new NotesSession();
nSession.setFullAdminMode( "SERVERNAME", true );

But don’t forget: These calls are native API calls and can easily crash your system.

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

Veröffentlicht unter Agenten, Java | Verschlagwortet mit , | Ein Kommentar

Java: Reflection Madness

A very interesting presentation about reflection in Java: Reflection Madness from Dr. Heinz M. Kabutz. You can find the slides here.

Veröffentlicht unter Java | Verschlagwortet mit , | Schreib einen Kommentar