Author:andreas.schaefer@madplanet.com>
<This How To serie is about how to develop EJX plugins with the help of Rickard's AWT, XML and BeanContext and NOT about how to use EJX and its plugins (at least for now)!!
Next steps
EJX (created by Rickard Oeberg and are available at his DreamBean Website: www.dreambean.com) is a launcher for JavaBean plugins that are written following the Glasgow specification, in particular the Extensible Runtime Containment and Services Protocol. This document is intended for programmers that want to write plugins for EJX, and will (try to) explain the insights of the bean context hierarchy of EJX, and also classloader issues regarding this hierarchy.
com.dreambean.ejx.editor.Main is the launcher for EJX. It does the following:
creates a new URLClassLoader, whose parent is the current context classloader
all files under ../lib and ../lib/ext are added to this URLClassLoader (PENDING: really all files or only jars)
the class com.dreambean.ejx.editor.EJX is instantiated using the new context class loader (ie the URLClassLoader)
All plugins you would like to show in the EJX framework must be under ../lib or ../lib/ext, so that their classes can be loaded through the context class loader. If this is not the case, your plugin is not even shown in the EJX first window, where you can choose, among the available plugins, which one you want to use.
Every EJX plugin is archived in a jar and must have an entry in the manifest file that reads:
EJX-plugin: <factory-class-name>
where <factory-class-name> is the fully qualified name of the ResourceManagerFactory implementation that can instantiate the ResourceManager implementation for that plugin.
Following the Glasgow specification, JavaBeans can be logically grouped in containers, called BeanContext. A BeanContext can contain other nested BeanContext, or directly BeanContextChild JavaBeans. While normal JavaBeans can be added to BeanContexts, to obtain the full potentiality of the new framework, they should implement the BeanContextChild interface. A BeanContextChild is normally a terminal child of the containment hierarchy, so it cannot have nested JavaBeans. JavaBeans, being they BeanContext or BeanContextChild can be added to or removed from a BeanContext, and notification of these events is delivered to registered membership listeners.
A BeanContext can expose services to its children, services that can easily accessed by them simply specifying the interface that represent the wanted service. The interfaces that provides this support are BeanContextServices and BeanContextServiceProvider.
BeanContextServices is a BeanContext with the possibility to be queried for services that it hosts. BeanContextServiceProvider is the service hosted by a BeanContextServices. Services can be added or removed from a BeanContextServices, and notification of these events is delivered to registered service listeners.
Within this framework, JavaBeans can obtain a reference to the BeanContext in which they are hosted and thus be aware of the environment in which they're running; plus they can query the BeanContext for services, if the BeanContext hosting them is a BeanContextServices. If this BeanContextServices does not have the requested service, the request goes up in the hierarchy, eventually finding the BeanContextServices that provides the service.
As you may have guessed, com.dreambean.ejx.editor.EJX is a BeanContextServices instance, and is the root of the bean context hierarchy of the application.
It hosts 2 services:
Direct children EJX are the plugins, that implements the ResourceManager interface. ResourceManager extends BeanContextChild so that implementors can be directly plugged in the containment hierarchy, but normally implementors of the ResourceManager interface implements BeanContextServices, to provide plugin-specific services to their nested JavaBeans.
PENDING: add a figure / schema of the containment tree with different colors representing services
We saw the bean context hierarchy, but what you can see on the screen is not totally related to it (though there is a relationship). How can EJX show the GUI for JavaBeans component seamlessly ?
Every JavaBean that wants to display a GUI in EJX must implement either BeanContextContainerProxy or BeanContextChildComponentProxy.
com.dreambean.ejx.editor.EJX implements BeanContextContainerProxy and its getContainer() method expose a JDesktopPane of a JFrame (also held by com.dreambean.ejx.editor.EJX [NOTE: this JFrame is not exposed (as of 15 Nov 2000), so it is not accessible from nested JavaBeans if they want play with the menu bar. I have in mind to change this and indirectly expose the JFrame as a EJX service]).
JDesktopPane is the specialized class that hosts JInternalFrames. Normally plugins implement the BeanContextChildComponentProxy interface returning a java.awt.Component (subclass) that can be added to a JInternalFrame and finally added to the JDesktopPane.
The difference between BeanContextChildComponentProxy and BeanContextContainerProxy is that the former is implemented by JavaBeans whose enclosing BeanContext is responsible to call getComponent and add the resulting java.awt.Component to some existing GUI, while the latter is implemented by JavaBeans whose enclosed JavaBeans are responsible to call getContainer() and add to this java.awt.Container GUI components taken from somewhere else. The former says "I know my children, let's take their GUI components and add them here", the latter says "I know my parent, let's add this GUI components to its GUI container".
EJX/AWT written by Rickard Oeberg
Both packages are created by Rickard Oeberg and are available at his DreamBean Website: www.dreambean.com. Both packages are heavily used in jBoss do create/maintain EJB descriptor and other XML files. The reason or motivation for me to write this HowTo was that I struggle to understand EJX and AWT. On the other hand Rickard was so busy with other stuff that I had to dig throug myself and to save time for other members of jBoss I started writing this HowTo. This document is still under construction and will maybe never be finished.
Idea of EJX
EJX is a package and runtime environment enabling you to create a plugin to add new functionality and new GUI elements. EJX will dynamically lookup for plugins on the predefined package /lib/ext and load them for you. Whenever you create a new file or open a given file it will instantiate your plugin and show as an Frame within the EJX framework. EJX uses XML but at the moment this is not quite clear for me but I am working on it (AS 9/15/00),
Idea of AWT
AWT (or here called Advanced Window Toolkit and do not mix it up with java.awt.*) enables you to use an uniform GUI Environment and to use BeanContext with an easy to write XML definition file. I am still at the beginning to understand AWT and EJX but I will upgrade this document as soon as I have more information and examples.
Based on the first draft of this document I separated the core EJX stuff from the EJX examples to make it a little bit more clear. Afterwards I implemented these changes in the EJX module of jBoss CVS server. If you now download the EJX module you can create a slim release of EJX without the examples. If you need them you can just jump to the examples directory and build the example from there (one by one) and only the examples you want or need.
To go through this document download the EJX module from the jBoss CVS server. Attention: Before you start with compiling any examples you have to run the compilation of the core project first. For this go to the "ejx/src/build" and start the build.bat file. This compiles the core project, copies the necessary jar-files to the right place and creates the necessary ejxeditor.jar file. Now you are ready for the examples.
This example can be found under "ejx/examples/plain.pane". This was my first example and the goal was to creat a Panel within EJX framework to display just a simple text. I used this example to find simplest example within EJX. According to the EJX spec the only necessary thing you have to to is:
Create a class extending the com.dreambean.ejx.FileManagerFactory interface
Create a class extending the com.dreambean.ejx.FileManager interface
Create an appropriate Manifest file with looks like this for this example: Class-Path: awt.jar ejb.jar EJX-plugin: com.madplanet.plainPane.FileManagerFactoryImpl Name: com/madplanet/plainPane/ Specification-Title: PlanePane 0.1 Specification-Version: v0.1 Specification-Vendor: MAD plaNET plc Implementation-Title: ejx-plain-pane Implementation-Version: build1 Implementation-Vendor: MAD plaNET plc
Put these two classes and the Manifest file into a jar-file (name does not matter but I always name it this way: ejx.<ProjectName>.jar).
And last but not least the just created jar-file into the ejx/dist/lib/ext directory.
Now the only thing left is to start EJX (go to ejx/dist/bin directory and start EJX with "java -jar ejx.jar"). When the EJX Frame comes up go to file/new and select "Plain Pane XML" and you will see our plugin coming up. Now let's have a closer look to the classes we created in our plugin.
Implements the com.dreambean.ejx.FileManagerFactory and enables the EJX framework to select the right file for you. But now let's delf into the code
Classes to be imported
import java.io.File; import javax.swing.filechooser.FileFilter; import com.dreambean.ejx.FileManager; import com.dreambean.ejx.FileManagerFactory;
Class definition (to extend this class from FileFilter is just convenience because it is needed either way):
public class FileManagerFactoryImpl extends FileFilter implements FileManagerFactory
These methods must be implement due FileManagerFactory interface. The first method creates the FileManager when a file is selected or in a given directory a new one can be created. The second method returns a FileFilter to select a file or directory and the last is used to get a name for the Plugin to select the right one.
public FileManager createFileManager() { return new FileManagerImpl( this ); } public FileFilter getFileFilter() { return this; } public String toString(){ return "Plain Pane XML"; }
Implements the com.dreambean.ejx.FileManager and enables the plugin to decide what GUI element to display. For each file or directory selected a new instance of this class is created.
Classes to be imported
import java.awt.BorderLayout; import java.awt.Component; import java.beans.beancontext.BeanContextServicesSupport; import java.io.File; import javax.swing.JPanel; import javax.swing.JLabel; import com.dreambean.ejx.FileManager; import com.dreambean.ejx.FileManagerFactory;
I am only so pitty about what classes are imported to show you at the header where the used classes are coming from. Constructor of the class:
FileManagerImpl( FileManagerFactory pCaller ) { mFactory = pCaller; }
Methods must be overwriten by the class, The important part is the getComponent() method which is called by the EJX framework to get the GUI component to be displayed.
public boolean isChanged() { return true; } public void createNew() { } public void load( File file ) throws Exception { } public void save( File f ) throws Exception{ } public File getFile() { return null; } public void setFile( File pFile ) { } public FileManagerFactory getFactory() { return mFactory; } public Component getComponent() { JPanel lPane = new JPanel( new BorderLayout() ); lPane.add( new JLabel( "<HTML><BODY><H1>Hello World</H1>" + "<H2>Next Step</H2></BODY></HTML>" ), BorderLayout.CENTER ); return lPane; }
This example can be found under "ejx/examples/simple.component". This example is an introduction to AWT and how it can be used to define a GUI by the XML description file, compiled and display on the Panel in the EJX framework. To shorten the further discussion I only show the important stuff having changed or is new.
The only thing with AWT you have to consider is that you have to use XMLBeans to compile the BeanInfo XML description into a Java class and then to compile it to a java bytecode class. For that have a look at the build.xml and look for xmlbeans.
According to the AWT spec the only necessary thing you have to to is:
Create an Bean Info XML description file like this: <bean class="com.madplanet.simpleComponent.MainPane" displayname="Simple Component's Main Pane" iconcolor16="/images/container.gif"> <property name="FirstProperty" class="java.lang.String" displayname="First Property"/> <property name="SecondProperty" class="java.lang.String" displayname="Second Property"/>
Create a GUI component class and add the GenericCustomizer to it (as parameter you have to pass the class instance which is referred above (here it is com.madplanet.singleComponent.MainPane). There are other classes you can use but at the moment I have no more informations.
User XMLBeans to create the java sourcecode from the XML beaninfo. The newly created java classes are named like the referred class but with Beaninfo at the end (same package structure).
That's it.
Like the one before except the getComponent() method:
public Component getComponent() { // Create the Property Container and return its GUI component return new MainPane().getComponent(); }
This class now creates the GUI component using AWT to display the properties this class have.
Classes to be imported
import java.awt.BorderLayout; import java.awt.Component; import java.beans.beancontext.BeanContextSupport; import java.beans.beancontext.BeanContextChildComponentProxy; import javax.swing.JPanel; import com.dreambean.awt.GenericCustomizer;
This class has to supclass the BeanContextSupport
public class MainPane extends BeanContextSupport
There are the properties the Main Pane offer and which can then be set by GUI component defined by the Bean Context Attention: All the properties in the BeanInfo XML file need here a public getter and setter method with the appropriate type.
public String getFirstProperty() { return mFirstProperty; } public void setFirstProperty( String pToSet ) { mFirstProperty = pToSet; } public String getSecondProperty() { return mSecondProperty; } public void setSecondProperty( String pToSet ) { mSecondProperty = pToSet; }
This method returns the GUI component which contains the Generic Customizer need to display the properties of this instance accordingly to the Bean Info XML description mentioned above.
public Component getComponent() { JPanel lPane = new JPanel( new BorderLayout() ); lPane.add( new GenericCustomizer( this ), BorderLayout.CENTER ); return lPane; }
Eh voilà, that's it. When you build this example, start EJX and select Simple Component XML you will see two lines with the tag "First Property" and a Text Field (and also for Second Property).
That's all for now. In the next step I will delf further into AWT and how you can use the Bean Info XML description to describe more advanced application. In addition I will then use the save() and load() method to load XML files which are the persistent part of a plugin.
I anything is wrong or not correct please contact me at andreas.schaefer@madplanet.com. Also if you want to know more in detail or have a request for changes in this HowTo document.
In this How To I will discuss the use of the BeanContext und AWT to create GUIs and GUI components within EJX but you can also use AWT outside of EJX.Attention: please note that I always mean Rickard Oeberg's AWT and not the Java AWT!
The AWT provides you with the following opportunities:
This seems to be simple but combining them together with the Java Bean Support and ordinary Swing coding leads to advanced GUIs like the actual version of EJX used in jBoss (I am not taking about the lack of User guidance but about the abilities of the GUI to display and edit the data).
My biggest problem to understand EJX/AWT was that you have to deal with EJX, AWT, Java Bean Support, XML and Swing coding. Therefore I started from the simplest example to understand each component, its function and how to use it. In the "Getting-Started" How To I showed how to create an EJX plugin and then create a basic Bean GUI. Now I go a step further to dynamically manage GUI components, use of more BeanInfo features and to edit properties with the AWT property editors. So let's start it!
From the last example of the first How To create and remove GUI Components (Tabbed Panes) dynamically and edit the properties of the data instance with the AWT editors.
First of all I want correct I mistake in the previous example. In the MainPane class it is said that getComponent() is implemented because of BeanContextChildComponentProxy which is not implemented in the class but it works because the caller does not relay on this fact. But in this example I fixed this. The question but still remains why it should (will maybe be explained later).
This example is really not a good example how to use EJX but it shows how to use EJX/AWT. The idea is to use a tabbed pane to display different views in our EJX frame. In addition the user can create and remove the tabbed pane as long as there is at least one left. And last but not least the user should be able to select a value through an editor instead of a plain input field.
As we saw in the last example the initial GUI component to show the plugin is created in the ResourceManager on the getComponent() call which is called by the BeanContext structure we use. But instead of create a Panel which just keeps the GUI of the plugin we create now a viewer of the plugin to display itself. This makes the design more flexible because it is now defined in the Viewer instead of the ResourceManager where it does not belong. Now the viewer is an inner class because it is so closely related to its outer component that it makes sense to be there.
The construct following is a little bit ugly and should not be MADE this way but it is enough for us. The ResourceManager getComponent() now calls the MainPane's getComponent() method and this instantiate its viewer and returns this Viewer as the GUI component to be shown.
When the users now hits the create a new Tab or remove this tab the appropriate method is called (by the BeanContext) and it will create a new MainPane instance and adds its also created Viewer instance as a new Tab to the tabbed pan or remove the actual shown tab from the tabbed pane. As you can see above the buttons we have a text input field and a drop down box letting the user select between two items. Both are AWT editors.
First lets have a look at the changes in the ResourceManager where the getComponent() just instanciate the MainPane and returns its GUI component.
public Component getComponent() { // Create the Property Container and return its GUI component return new MainPane().getComponent(); }
Now let's look at the new BeanInfo description of the MainPane:
<bean class="com.madplanet.tabbedEditor.MainPane" displayname="Tab Pane and Editor's Main Pane" iconcolor16="/images/container.gif"> <property name="FirstProperty" class="java.lang.String" displayname="First Property" propertyeditor="com.dreambean.awt.editors.TextEditor"/> <property name="SecondProperty" class="java.lang.String" displayname="Second Property" propertyeditor="com.madplanet.tabbedEditor.editors.SecondPropertyEditor"/> <method name="createTab" displayname="Create a new Tab"> <parameter displayname="Title"/> </method> <method name="removeTab" displayname="Remove this Tab"> </method> </bean>
As you can see there are property editors settings for the first and second property and the second property uses its own editors. In addition you have methods listed which appears as buttons in the GUI because we use the GenericCustomizer.
The new editor is just a simple subclass of the AWT TagsEditor defining what items it has to show and to what value the item relate to (you can use any Java Object you like):
package com.madplanet.tabbedEditor.editors; import com.dreambean.awt.editors.TagsEditor; /** * Editor to select the Second Property in a DD - editor */ public class SecondPropertyEditor extends TagsEditor { // Constructors -------------------------------------------------- public SecondPropertyEditor() { super(new String[] {"First Selection","Second Selection"}, new Object []{"First", "Second"}); } }
And as "Grande Finale" we come to the heart of the plugin to the MainPane class.
The new viewer class is an inner class to the MainPane and creates a GUI to display the instance of its outer class instance. It keeps a list of outer class instances to find the index of the tab to be removed. The setObject() creates a new tab and memorize the given outer class. The removeTab() looks for the given outer instance and removes its related tab (by this index).
public class Viewer extends JTabbedPane implements Customizer { // Attributes --------------------------------------------------- private Vector mDataObjectList = new Vector(); // Customizer implementation ------------------------------------ public void setObject( Object pDataObject ) { // Init UI addTab( "Main", new GenericCustomizer( pDataObject ) ); mDataObjectList.addElement( pDataObject ); } /** * Removes the given Tab with the given Data Object * from the Tabbed Pane * * @param pDataObject Tab with this Data Object has to be removed if found **/ public void removeTab(Object pDataObject ) { int lIndex = mDataObjectList.indexOf( pDataObject ); if( lIndex >= 0 ) { remove( lIndex ); mDataObjectList.removeElementAt( lIndex ); } } }
These are the new methods (already defined in the BeanInfo see above) which are called if the appropriate button is pressed.
public void createTab( String pTitle ) throws Exception { System.out.println("Create new Tab with title: " + pTitle); MainPane lNewPane = new MainPane(); lNewPane.mCustomizer = mCustomizer; lNewPane.mCustomizer.setObject( lNewPane ); } public void removeTab() { System.out.println( "Remove this tab"); ( (Viewer) mCustomizer ).removeTab(this ); }
This is more ore less EJX and AWT. But it does not end here. The power of EJX lays in the BeanContext and how you combine EJX, BeanContext and AWT. You maybe think what's about XML and we will come to XML soon but (in my opinion) this is not a core part of EJX and AWT just use it but just as the name of the EJX base classes said they manage resources (or earlier just files). Therefore it must be a way do deal with resources and especially with XML resources.