Jungi's Blog...


« Securing Web Service... | Main | Export EJB client... »
Friday Jul 28, 2006

Java EE application client on top of NetBeans Platform

For this article we will need FeedReader application (FeadReader Application project) and some empty Java EE enterprise application (feedreader-ear project) with application client (feedreader-ear-app-client project) , so let's create them somewhere on the disk.

Because NetBeans platform as well as FeedReader application is just a set of jars it should be possible to take all these jars, package them to enterprise application "/lib" directory (as it is common place for libraries in enterprise applications defined by Java EE 5 specification) and from application client's main class just call org.netbeans.Main.main to start an application. Let see what will happen if we follow these steps:

  1. run Build JNLP Application action on FeadReader Application project
  2. open build.xml of feedreader-ear project and put there following pre-dist target:
        <target name="pre-dist">
            <!-- feedreader.home must point to FeedReader Application home directory -->
            <property name="feedreader.home" value="../feedreader-suite"/>
            <mkdir dir="${build.dir}/lib"/>
            <copy todir="${build.dir}/lib">
                <fileset dir="${feedreader.home}/build/jnlp/app" includes="*.jar" />
                <fileset dir="${feedreader.home}/build/jnlp/branding" includes="*.jar" />
                <fileset dir="${feedreader.home}/build/jnlp/netbeans" includes="*.jar" />
            </copy>
        </target>
          
  3. call NetBeans Platform's main class from application client's main class:
        public static void main(String[] args) {
            try {
                org.netbeans.Main.main(new String[] {"--branding", "feedreader"});
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
          
    To be able to compile this we have to add boot.jar from NetBeans Platform to appclient project libraries. But do not include it in packaged application as it will be added to lib directory in resulted ear by pre-dist target created in step 2. See attached picture, where to "mark" library to not being packaged in resulted EAR.

Now if we run feedreader-ear project we will find following problems:

  • logged java.lang.Error during application startup
  • application user directory is not created in correct location (at least on Windows platform)
  • warning that Database Schema module cannot be installed because of missing dependencies
  • java.io.FileNotFoundException: Windows2/WindowManager.wswmgr is thrown right after opening application's main window
  • application's log after its very first run

So, it does not look as simple as it seemed to at the beginning. Let see what all these problems means and if they can be somehow fixed or workarounded:

logged java.lang.Error during application startup
Full stacktrace is:
java.lang.Error: factory already defined
        at java.net.URL.setURLStreamHandlerFactory(URL.java:1074)
        at org.netbeans.core.startup.Main.initializeURLFactory(Main.java:126)
        at org.netbeans.core.startup.Main.start(Main.java:281)
        at org.netbeans.core.startup.TopThreadGroup.run(TopThreadGroup.java:96)
        at java.lang.Thread.run(Thread.java:595)
   
According to comment found in the source of org.netbeans.core.startup.Main we can ignore this as it seems to have no effect on application being run (we can take it like other informational exceptions).
 
application user directory is not created in correct location (at least on Windows platform)
We can find application log in C:\Documents and Settings\%USERNAME%\var\messages.log instead of in usual C:\Documents and Settings\%USERNAME%\$appname\var\messages.log. We can fix this we by adding following method to appclient's main class and call it before running NetBeans platform. It is slightly modified version of similar method in org.netbeans.modules.apisupport.jnlplauncher.Main
    /** Sets value of netbeans.user property.
     */
    final static void fixNetBeansUser() {
        String userDir = "${user.home}/.feedreader-ear"; // NOI18N
        final String PREFIX = "${user.home}/"; // NOI18N
        int uh = userDir.indexOf(PREFIX);
        if (uh == -1) {
            return;
        }
        String newDir =
                userDir.substring(0, uh) +
                System.getProperty("user.home") + // NOI18N
                File.separator +
                userDir.substring(uh + PREFIX.length());
        System.setProperty("netbeans.user", newDir); // NOI18N
    }

warning that Database Schema module cannot be installed because of missing dependencies
The cause of this problem is dbschema.jar bundled with GlassFish (see %GLASSFISH_HOME%\lib). This jar comes from NetBeans, it is regular NetBeans module and it is discovered by the platform during its startup (not sure why it is being discovered by the platform yet - maybe because they're on the application's startup classpath). Platform also tries to find other modules this "module" depends on, but they are not found, thus this warning.
Users which will be running application client using appclient script will be able to close this dialog, but users who will try to use Java Web Start capability of application clients in GlassFish will be not able to continue because the warning dialog will not be shown correctly - there will be nothing in the dialog - no text, no buttons, so they will be forced to kill whole java process.
Workaround: one possible way how to get rid of this warning could be removing the library from the ACC container - this can be done by adding <exclude name="lib/dbschema.jar"/> to %GLASSFISH_HOME%\lib\package-appclient.xml script. Although this may help, I did not test this and I do not recommend it. Other way how this can be workarounded is to correctly set netbeans.dirs property and to use custom ClassLoader to load libraries and to start NetBeans Platform using reflection.
 
java.io.FileNotFoundException: Windows2/WindowManager.wswmgr is thrown right after opening application's main window
I think that this is caused by some bug in GlassFish, so see https://glassfish.dev.java.net/issues/show_bug.cgi?id=878 for opinions of people from GlassFish team.
Workaround: create custom classloader.

Last two problems can be workarounded by using custom ClassLoaders, so here is how the application client's main method can look like:

    public static void main(String[] args) {
        //create new "top-level" classloader which will not see dbschema.jar
        //get original classloader
        ClassLoader originalClassLoader = new Main().getClass().getClassLoader();
        URLClassLoader applicationClassLoader = (URLClassLoader) (originalClassLoader.getParent());
        URL[] us = applicationClassLoader.getURLs();
        List l = new ArrayList();
        for (int i = 0; i < us.length; i++) {
            if (!us[i].getFile().endsWith("dbschema.jar")) {
                l.add(us[i]);
            }
        }
        //our new "top-level" classloader
        ClassLoader rootClassLoader = new URLClassLoader(l.toArray(new URL[l.size()]), null);
        //our custom "NB platform" classloader
        l.clear();
        for (int i = 0; i < resources.length; i++) {
            URL u = originalClassLoader.getResource(resources[i]);
            l.add(u);
        }
        ClassLoader loader = new URLClassLoader(l.toArray(new URL[l.size()]), rootClassLoader);
        
        //fix userdir
        fixNetBeansUser();
        //fix netbeans.dirs property - to get rid of warning about dbschema module
        //when running using appclient script
        URL url = originalClassLoader.getResource(resources[0]);
        System.setProperty("netbeans.dirs", new File(url.getFile()).getParentFile().getAbsolutePath());
        //start our NB platform based application
        try {
            Object[] argss = new String[] {"--branding", "feedreader"};
            Class c = loader.loadClass("org.netbeans.Main");
            Method m = c.getMethod("main", new Class[] { String[].class });
            m.invoke(null, new Object[] {argss});
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

Full code of application client's main class is here. One more required change has to be done in build.xml modified in step 2 at the beginning of this article if you want to just reuse this class - replace ${build.dir}/lib with ${build.dir}/netbeans. Now you should be able to run FeedReader demo application using appclient script as well as using Java Web Start feature in GlassFish.

Comments:

Hi, AFAIK it is also possible to set the $USERDIR by passing --userdir <path-to-userdir> to org.netbeans.Main as well as the branding.

Posted by Gergely Dombi on August 04, 2006 at 10:13 AM CEST #

Post a Comment:
Comments are closed for this entry.

Today's Page Hits: 39