A Customized NetBeans Window Manager: Drag & Drop Top Components In Floating/Separated Windows!

I love NetBeans and the NetBeans Platform for creating Rich Client Applications.  I first started using NetBeans while searching for a new application framework to port an existing Swing application.

I needed more sophisticated window management.  The NetBeans Platform provided a lot of the functionality I was looking for: an easy way to create modular extensible applications with dock-able windows and application life-cycle management.

As with any framework I eventually found I wanted behavior that was not provided.  Fortunately NetBeans is Open Source and that makes it extendable.

I was originally drawn to NB because it provided an API for drag & drop window partitioning that would allow my users to customize their content in a way that would help improve their productivity.

The NetBeans Window Manager works great for applications built around a single main-window but it’s support for multiple windows is limited.


A customized Main Window Layout

Floating windows are restricted to a single tabbed-pane.  I can have multiple documents in a floating window but I can’t arrange them like I can in the main-window.

A floating window

The NetBeans WindowManager was created before large or multiple monitors were common and was designed around the concept of a single main-window with a main editing area and peripheral utility views.

The platform was originally created as a framework for developing the NetBeans IDE and was later modified to allow more generic application development.

The Window Manager contains some fairly complex code.  Converting the existing internal models and interfaces to provide multiple window support wasn’t an easy task.   While testing my changes I started to discover a number of existing bugs or inconsistent behaviors in the original Window Manager that I wanted to address.

Z-Order

Swing doesn’t track the z-order of window components. In the netbeans DND package There is a ZOrderManager class that is used to keep track of window order. There’s a comment at the top of the class that states:

* Note, manager is NetBeans specific, not general. It automatically attaches
* main window and expects that all registered zOrder are always above this main
* window.

However dragging an editor top component out of the main window creates a Frame based window. A frame can go behind the main window and this breaks the z-order this class was designed to track (windows are ALWAYS above the main window).

NetBeans shows an incorrect drop location when a floating window is behind the main window as seen here:

In the screen shot below multiple drop location indicators are visible at the same time!

When NetBeans is restarted it restores the previous state. Modes and Floating windows that were open when the application closed are reopened. However NetBeans does not persist the Z-Order information. The windows to open are returned in an un-ordered set. If you had overlapping windows they may overlap differently after a restart.

Views & Editors

NetBeans has the concept of views and editors – although this concept is not at the TopComponent level as you might expect. NetBeans has the concept of View and Editor modes (dockable regions).

Originally a TopComponent in an editor mode could only be moved into another editor mode and a TopComponent in a view mode could only be moved into another view mode. The two modes have slightly different look & feels.

The editor mode shows icons on the tabs and has a maximize button and tab navigation buttons.

The view mode does not show icons on the tabs and has a minimize button. The view tabs do not scroll they compact in size to keep all tabs visible on a single row.

The NetBeans Window Manager was updated to allow Top Components to be moved anywhere allowing the user more control over their screen real-estate. Unfortunately in NetBeans it’s not the TopComponent that determines if it is a view or an editor – it’s the mode that determines the type.

I find this leads to some really confusing and inconsistent behavior.

If I drag a text-editor out of an editor mode it creates a floating window based on a Frame. The frame can be above or below the main window and the text editor shows a status-bar where I can see my insert location.

If I move my text-editor into a view mode and then drag my text-editor out of the view mode it creates a floating window based on a Dialog. The dialog always floats above the main-window. It will minimize/hide when I minimize the main-window. It cannot be maximied. It does not have a status-bar where I can see my cursor position!

The Frame shows a title and the Dialog does not.

StatusLine Fix

In the Editor module there is a StatusLineFactories class that determines when this additional status line should be shown.

It performs the following test:

    boolean underMainWindow = (SwingUtilities.isDescendingFrom(component,
                    WindowManager.getDefault().getMainWindow()));

This works for floating frames however a dialog has a parent and the parent of a floating dialog is the main-window!

I changed that test to the following:

            Container ancestor = SwingUtilities.getAncestorOfClass(Window.class, component);
            boolean underMainWindow = ancestor != null && "NbMainWindow".equals(ancestor.getName());

This finds the window the component is within and determines if the window is the main-window or not.

Now my dialogs show a status line when I’m displaying a TopComponent that is an editor:

I would like my editor TopComponents to always have an icon – no mater where I place them.

Here I can easily identify my editor TopComponents because they both have icons:

NetBeans Application With Optional Module

This is a demonstration of a NetBeans application that can call code in ModuleB if ModuleB is installed/activated but also operate without ModuleB.  No reflection is used. ModuleA can invoke classes defined in ModuleB.

Watch the application in action:

This is a hack to circumvent the NB Module class loading restrictions. I’m not sure what issues this could lead to this is purely for demonstration purposes that might help lead to a better solution.

I created a ModuleSuite. I called it MyVendorSuite and added one Module to it called VendorModule (think of this as ModuleB). VendorModule contains one class with a single method that returns a string (as shown below)

Click to view enlarged image

There’s nothing special about this Module. I packaged it as an NBM that I could install via the plugins page in the UI.

The code from our VendorClass object…

package com.delta.vendor;

public class VendorClass {
    public String sayHello() {
        return "Hello from your vendor";
    }
}

I created an Application Suite called MyApp with a single MyAppMod module (think of this as ModuleA).
MyAppModule contains a single window/TopComponent class from which we will attempt to use ModuleB classes.

TopComponent code snippet:

    public testTopComponent() {
        initComponents();
        setName(Bundle.CTL_testTopComponent());
        setToolTipText(Bundle.HINT_testTopComponent());

        
        setLayout(new BorderLayout(0,0));
        JLabel label = new JLabel();
        add(label, BorderLayout.CENTER);
        
        
        // we'll want a utility method to perform this check
        boolean isMyModuleEnabled  = false;
        Collection<ModuleInfo> modules = (Collection<ModuleInfo>) 
            Lookup.getDefault().lookupAll(ModuleInfo.class);
        for(ModuleInfo module: modules) {
            if(module.getCodeName().equals("com.delta.vendor")) {
                isMyModuleEnabled = module.isEnabled();
                break;
            }
        }

        // isMyModuleEnabled will be false if the module is not installed 
        // OR the module is deactivated! If we just used a try catch block 
        // catching ClassNotFound it would only work when the module is 
        // uninstalled.  If the module is deactivated, the class files can 
        // still be loaded and used
        if(isMyModuleEnabled) {        
            VendorClass instance = new VendorClass();
            label.setText(instance.sayHello());
        } else {
            label.setText("Module B must be deactivated or not intalled!");
        } 
    }

Notice we are using the VendorClass code directly.

I modified the project.xml (Project Metadata) for MyAppModule (ModuleA) as shown:

Click to view enlarged image

            <code-name-base>com.delta.app</code-name-base>
            <suite-component/>
            <module-dependencies>
                <dependency>
                    <code-name-base>com.delta.vendor</code-name-base>
                    <compile-dependency/>
                </dependency>

Basically I removed the Runtime dependency for ModuleB.  This allows me to use the class names in ModuleA at compile time but doesn’t require ModuleB to be installed at runtime.

I added a Class-Path: entry to the Module Manifest for Module A as shown:

Click to view enlarged image

Manifest-Version: 1.0
AutoUpdate-Show-In-Client: true
OpenIDE-Module: com.delta.app
OpenIDE-Module-Localizing-Bundle: com/delta/app/Bundle.properties
OpenIDE-Module-Requires: org.openide.windows.WindowManager
OpenIDE-Module-Specification-Version: 1.0
Class-Path: ../../../../../../Users/redacted/AppData/Roaming/.myapp/dev/modules/com-delta-vendor.jar

The class path entry is a RELATIVE path. For this to work we need to know where our nb userdir will be relative to our application install (and since we can control the userdir location from etc/app.conf that should be something we have control over). The relative path needs to point to the location our vendor-module jar file will be located at once installed!

For this demonstration:

my user dir was: C:\Users\redacted\AppData\Roaming\NetBeans\dev
my app was installed: C:\Users\redacted\Desktop\myapp
my NBM install resulted in: C:\Users\redacted\AppData\Roaming\.myapp\dev\modules\com-delta-vendor.jar

Finally I packaged MyApp as a zip distribution and extracted the files into a directory so I could launch the application.

NOTE:  This solution cannot be run from within the IDE but it does work for an installed application!

NetBeans Optional Module

Creating an application that uses an optional module when it is available

Create a platform suite application – for this demonstration I called it MyAppWithOptionalModule

Add Module A

Create an Installer/Activator
This is for quick demonstration purposes, you don’t need to use an Intaller/Activator this code could be placed in your own class!

Add a simple System.out.println(“Hello From Module A”); message so we can see something happening in ModuleA.

If you run this application you should see “Hello From Module A” in the output window (as seen above).

In order to make ModuleB optional we need a shared interface that we’ll define in another Module (Module C).

Create a new Module Suite:

I’ve called this shared Module Suite ModuleC:

Create a new Module called ModuleC within the ModuleC Module Suite.

Create a new Interface I’ve called it ModuleBInterface.
Define a method for this example I’ve called it sayHelloFromModuleB.

(I’ve not shown all these steps but you should already know how to create a new interface or class within a project)

You should now have something like this…

Goto ModuleC’s Properties and select API Versioning. Make Module C public:

Build the ModuleC Module Suite.

Goto the first Application Suite that we created
I called mine MyAppWithOptionalModule.

Right click and select Properties.
Goto Libraries and press the “Add Project Button”

Select the ModuleC Module Suite we created above:

Press OK, you should now see your new shared module in the list of available libraries:

Go back to our Installer (or your class) in ModuleA and we’ll add some code that uses ModuleB if it exists (which at this point it does not):

We are going to use the Lookup API so right click on Module A, select Libraries, Add Dependency and Select Lookup API.

We are also going to need ModuleC so lets add that too!

Add some more code to our Installer class as shown below:

This uses the ModuleB interface we defined (in ModuleC).
We fetch an instance of that class if there is one defined in the global lookup.
If we get back an instance (from Module B) we use it – otherwise we display a message that module B isn’t installed (just for demonstration purposes).

If you run the application you should see the new output (as seen in the image above).

Hello from Module A
Module B isn’t installed but that’s OK, we just won’t use it

Finally create ModuleB which will provide an implementation of the (optional) class we wan’t to use in ModuleA.

Create a new Module Suite:

I created a new Module Suite called ModuleB and added a new Module (also called ModuleB) to it.

Right Click on the ModuleB ModuleSuite properties and select Libraries. Add Module C as we did earlier.

Right Click on the ModuleB ModuleB and add a dependency on ModuleC

We are also going to need the Lookup API so lets add that too!

In ModuleB create a new Class – I’ve called mine ModuleBImplementation.

You should now have the following structure:

Modify the code so that we implement the ModuleBInterface interface we defined in ModuleC and add a ServiceProvider annotation as shown below:

Compile/Build all 3 projects.

Right Click on the ModuleB ModuleSuite and select Package as NBMs.

Make a note of the directory where the NBMs are created (shown in the output window as seen below):

Right Click on our Application and select Libraries.

Add Auto Update Services and Auto Update UI. These will allow use to install our optional Module B. It will add a Plugins menu item under the Tools menu of our GUI.

If we run our application again we notice it returns the same information as before – that Module B isn’t installed and therefore not used.

When we run – we also get a GUI window. In our GUI application select the Tools/Plugins menu item.

Goto the Downloaded Tab. Press the Add Plugins… button and navigate to the directory where our NBM is located.
(It was shown in the output when we performed the Package as NBM step above)

Press the install button and follow the wizard to install the plugin.

If we run the application again we’ll see the following in the output window:

Yay! Module A just used our optional Module B class.

Go back to the Tools/Plugins dialog and uninstall Module B.

Run the application again…

Once again we see Module A is running but it didn’t use the optional class defined in Module B because Module B is no longer installed!

Websphere 8 Application & Static Content

I just migrated an application to Websphere and wanted to serve some static files (.jar, .jsp, .cfg, etc.) from the same context root as my application.

I used the ExtendedDocumentRoot feature to serve static content from a directory located outside of my applications directory structure.

https://www.ibm.com/support/knowledgecenter/SSCKBL_8.5.5/com.ibm.websphere.nd.doc/ae/rweb_jspengine.html

I eventually settled on this solution:

Create a file called ibm-web-ext.xmi in the WEB-INF directory of the application.

<webappext:WebAppExtension xmi:version="2.0"
	xmlns:xmi="http://www.omg.org/XMI" xmlns:webappext="webappext.xmi"
	xmlns:webapplication="webapplication.xmi" xmi:id="WebApp_ID_Ext"
	reloadInterval="10" reloadingEnabled="true" fileServingEnabled="true"
	directoryBrowsingEnabled="false" serveServletsByClassnameEnabled="false"
	preCompileJSPs="false">
	<webApp href="WEB-INF/web.xml#WebApp_ID" />
	<fileServingAttributes xmi:id="FSA_1"
		name="extendedDocumentRoot" value="${CONTENT_DIR}" />
	<jspAttributes xmi:id="JSA_1" name="extendedDocumentRoot"
		value="${CONTENT_DIR}" />
</webappext:WebAppExtension>

The fileServingEnabled property wasn’t really necessary as the default value is true but I included it for clarity.
The fileServingAttributes element sets the directory to use for static files (not .jsp).
The jspAttributes element sets the directory to use for JSP files.

The ${CONTENT_DIR} property should be defined as an Websphere variable (you can specify different values for each node). The directory doesn’t have to be a variable it could be a relative or fully qualified path. Multiple directories can be specified using a comma separated list.

websphere

GrayLog – Monitoring Dashboard

GrayLog is an open source log management and analysis tool that can be used to monitor an applications health assist with debugging and identify trends.

To get started quickly GrayLog can be downloaded as a Virtual Machine image – already configured.  We will be using Virtual Box to run our instance.

Goto https://www.graylog.org

graylog.org website

graylog.org website

Press the “Install now” button and select the OVA Virtual Appliance.

In VirtualBox press CTRL+I or goto File/Import Appliance…

Select the OVA file we just downloaded and follow the wizard steps.

Once the VM runs you’ll be presented with an Ubuntu shell requesting login.

GrayLog shell

GrayLog shell

Login using the username “ubuntu: and the password “ubuntu”.

Lets secure things a little.

Issue the following commands:

sudo passwd root

Enter a new password for the root account.

sudo passwd ubuntu

Enter a new password for the ubuntu account.
This account is used to access the shell.

sudo graylog-ctl set-admin-password <password>

Enter a new password for the admin account.
This account is used to access the web-interface.

sudo graylog-ctl reconfigure

We are now ready to start using GrayLog…

GrayLog Web App

GrayLog Web App

 

Add an Input Stream

UDP/GELF Port 12000

 

LogMX + NetBeans

I just created a NetBeans Plugin that implements LogMX GotoSource functionality.

http://plugins.netbeans.org/plugin/69469/logmx-gotosource

LogMX is an intuitive and cross-platform tool, for developers and administrators analyzing log files. Using a nice and powerful graphical interface, LogMX parses, displays and monitors any logs from any source. The LogMX NetBeans Plugin integrates LogMX & NetBeans. When viewing a stack trace in LogMX you can view the related source code (passed from the NetBeans IDE) or you can click on the stack trace entry to open the corresponding file in your NetBeans workspace at the line the error occurred! See: www.logmx.com