Skip to main content

skip to main content

developerWorks  >  Open source | Java technology  >

Eclipse and HSQLDB: Embedding a relational database server into Eclipse, Part 2

How to write a plug-in that integrates the HSQLDB database server into the Eclipse Workbench

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Fernando Lozano, Independent consultant

04 Dec 2003

The first part of this series introduced the HSQLDB engine for embedded applications that can also serve as a development platform for projects targeting "bigger" databases. We created an Eclipse plug-in and started pre-existing tools from Workbench menus. In this second part, we'll look at how to use Eclipse features to overcome some limitations of the plug-in developed in the first part, such as allowing many concurrent active HSQLDB server instances, each one listening for connections on a different TCP port.

HSQLDB developer roles

It is easy to identify two kinds of developers interested in HSQLDB integration into the Eclipse workbench:

  1. Client developers, who simply use HSQLDB as a repository for data.
  2. Engine developers, who want to extend the SQL dialect recognized by HSQLDB by adding either new scalar functions or stored procedures.

Developers of the first kind need easy access to the HSQLDB JDBC driver so they can submit SQL statements. But it would also be nice to have a convenient way to start and stop different HSQLDB instances (say, two projects that need different sets of tables). The more advanced developers might also like to generate JavaBeans(TM) mapping attributes to table columns, and it would also be nice to generate Data Access Objects providing methods for querying and updating data in the database.

The needs of the second kind of developer involves the ability do add new Java classes to the the database engine. They'll also want to use the Eclipse debugger to set breakpoints and inspect variables inside their custom methods invoked by the database server.

For example, if I wish to add a scalar function that returns the CRC32 checksum of a string column, I have to create a class containing a public static method, as shown in Listing 1, and then it'll be available for use on SQL statements after the script in Listing 2 is executed.


Listing 1. Java class to extend the HSQLDB engine
package hsqldb.functions;

import java.util.zip.CRC32;

public class CrcUtil {
    public static long crc(String data) {
        CRC32 crc32 = new CRC32();
        crc32.update(data.getBytes());
        return crc32.getValue();
    }
}



Listing 2. SQL script file to add the new scalar function to the HSQLDB engine
CREATE ALIAS CRC FOR "hsqldb.functions.CrcUtil.crc";
GRANT ALL ON CLASS "hsqldb.functions.CrcUtil" TO PUBLIC;

Most features desired by the client developer are outside the scope of this series. There are many SQL monitors, OO-Relational mapping tools and other database utilities already available as Eclipse plugins, which are independent of the database and could be used alongside our HSQLDB plugin to provide a complete environment for this kind of developer. The focus for this series will be on features desired by the database engine developer.



Back to top


HSQLDB plugin, second release

The original plan for the second part of this series involved creating a specialized HSQLDB View, where many configurations (server port, user, password and database file path) could be edited, and each of them could be started or stopped individually. But when I realized most features were already available from the Eclipse JDT itself, if I simply let HSQLDB run as a standard Java Application, the plan changed radically.

The plan for this article is now is now to define a specialized kind of Java Project, where HSQLDB libraries are automatically made available, and tie to it actions to start the server and related tools (Database Manager and Script tool). Of course, HSQLDB connection parameters should be stored on a per-project basis, instead of only once for the whole workbench.

Project nature and actions

The Eclipse way to define a new kind of project is a Project Nature, which is one of the many extension points made available by the platform. Each project can have many natures, so there's no need to create a brand new project class, duplicating or inheriting functionality from the Java project class. You can just tell the workbench the new nature depends upon the already defined (by the JDT) Java Project Nature. The Java nature provides all tools for editing Java classes, creating unit test cases and debugging Java applications. The HSQLDB Engine nature provides the means to start, stop and configure HSQLDB.

Listing 3 shows the code snippet from plugin.xml that defines the new nature. It is associated with a class that must implement IProjectNature and which can add builders and other elements to the workbench, but for this second release HSQLDB plugin does not need to do anything. Anyway, the nature implementation class has to be provided otherwise the workbench won't add the nature to a project. We'll use the new nature simply as a way to make HSQLDB actions available (or not available) for specific projects.


Listing 3. plugin.xml manifest file snippet defining the HSQLDB Engine Project Nature
   <extension
         id="hsqldbEngine"
         name="HSQLDB Engine"
         point="org.eclipse.core.resources.natures">
      <requires-nature
            id="org.eclipse.jdt.core.javanature">
      </requires-nature>
      <runtime>
         <run
               class="hsqldb.nature.DBEngineNature">
         </run>
      </runtime>
   </extension>

Every Java project will get an HSQLDB menu. It will initially contain just one item, "Add HSQLDB Engine nature", as seen in Figure 1. If you have ever tried the EMF set of plugins from the Eclipse Consortium, you'll notice this is the same concept.


Figure 1. HSQLDB submenu from the Java Project context menu
HSQLDB submenu from the Java Project context menu

When this action is selected by the user, the menu will change to display more options, to start and stop HSQLDB, and to start the Database Manager in both standalone (in-process) or client/server mode, as seen in Figure 2. The "HSQLDB" menu at the top-level workbench menubar, which was defined by the first release of our plugin, will disappear, being replaced by the "HSQLDB" submenu on each Java project context menu.


Figure 2. HSQLDB submenu after the Engine nature has been added
HSQLDB submenu after the Engine nature has been added

Action visibility and enablement

In the first release of the HSQLDB plugin, each action had to check if it could actually be performed (not permitted to start two instances of the database server, for example). I already mentioned that Eclipse did not provide an easy way to programmatically set each menu item state, because the workbench tries to manage everything itself, delaying loading of the plugins to the last moment, when it has to actually invoke its code.

Now that each action is tied to a workspace resource (a Java Project), the workbench can figure out when each item should be visible or enabled, without invoking the plugin. The elements visibility and enablement can have children such as objectState that queries resource properties to set each action status. Listing 4 shows the plugin.xml snippet for defining five objectContributions:

  1. The "Run HSQLDB SQL Script" action, which is visible for any *.sql file inside a project which has the new nature.
  2. The "HSQLDB" submenu, visible for any Java Project, containing two groups of items. If both groups have actions associated to them, a separator line will be drawn.
  3. All HSQLDB actions originally defined on the first part of this series, such as starting and stopping the server, visible only when the Java Project has our newly defined nature.
  4. The "Add HSQLDB Engine nature", visible then a Java Project does not have the nature defined by the plugin itself
  5. And last, the "Remove HSQLDB Engine nature", visible then a Java Project has our new nature


Listing 4. plugin.xml manifest file snippet defining the HSQLDB menu actions
   <extension
         point="org.eclipse.ui.popupMenus">
      <objectContribution
            objectClass="org.eclipse.core.resources.IFile"
            nameFilter="*.sql"
            id="hsqldb.ui.SQLScriptFiles">
         <visibility>
            <objectState
                  name="projectNature"
                  value="hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Run HSQLDB Script"
               class="hsqldb.ui.popup.actions.HsqldbRunScript"
               menubarPath="additions"
               enablesFor="1"
               id="hsqldb.ui.HsqldbRunScript">
            <enablement>
               <objectState
                     name="projectSessionProperty"
                     value="hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineMenu">
         <menu
               label="HSQLDB"
               path="additions"
               id="hsqldb.ui.HsqldbProject">
            <separator
                  name="hsqldb.ui.group1">
            </separator>
            <separator
                  name="hsqldb.ui.group2">
            </separator>
         </menu>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineActions">
         <visibility>
            <objectState
                  name="nature"
                  value="hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Run Database Manager (Standalone)"
               icon="icons/dbmansa.gif"
               class="hsqldb.ui.popup.actions.RunDatabaseManagerStandalone"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.RunDatabaseManagerStandalone">
            <enablement>
               <not>
                  <objectState
                        name="sessionProperty"
                        value="hsqldb.ui.running">
                  </objectState>
               </not>
            </enablement>
         </action>
         <action
               label="Run Database Manager (Client)"
               icon="icons/dbman.gif"
               class="hsqldb.ui.popup.actions.RunDatabaseManager"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.RunDatabaseManager">
            <enablement>
               <objectState
                     name="sessionProperty"
                     value="hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
         <action
               label="Stop HSQLDB Server"
               icon="icons/stop.gif"
               class="hsqldb.ui.popup.actions.StopHsqldbServer"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.StopHsqldbServer">
            <enablement>
               <objectState
                     name="sessionProperty"
                     value="hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
         <action
               label="Start HSQLDB Server"
               icon="icons/start.gif"
               class="hsqldb.ui.popup.actions.StartHsqldbServer"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.StartHsqldbServer">
            <enablement>
               <not>
                  <objectState
                        name="sessionProperty"
                        value="hsqldb.ui.running">
                  </objectState>
               </not>
            </enablement>
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.JavaProjects">
         <visibility>
            <not>
               <objectState
                     name="nature"
                     value="hsqldb.ui.hsqldbEngine">
               </objectState>
            </not>
         </visibility>
         <action
               label="Add Database Engine nature"
               class="hsqldb.ui.popup.actions.AddDBEngineNature"
               menubarPath="hsqldb.ui.HsqldbProject/group2"
               enablesFor="1"
               id="hsqldb.ui.AddDBEngineNature">
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineProjects">
         <visibility>
            <objectState
                  name="nature"
                  value="hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Remove Database Engine nature"
               class="hsqldb.ui.popup.actions.RemoveDBEngineNature"
               menubarPath="hsqldb.ui.HsqldbProject/group2"
               enablesFor="1"
               id="hsqldb.ui.AddDBEngineNature">
         </action>
      </objectContribution>
   </extension>

The first objectContribution is included in the top-level context menu for IFile resources, while the second one defines the "HSQLDB" submenu for Java Projects. The remaining ones will be displayed on the submenu itself: the third one on group1 and the last two on group2, but only one of them will be visible for a given project. Figures 3 and 4 shows these objectContribution org.eclipse.ui.popupMenus extension point on the PDE manifest editor.


Figure 3. Actions defined by the plugin (#1/2) on the PDE manifest editor
Actions defined by the plugin (#1/2) on the PDE manifest editor

Figure 4. Actions defined by the plugin (#2/2) on the PDE manifest editor
Actions defined by the plugin (#2/2) on the PDE manifest editor

Besides displaying actions (objectContributions) only for the correct kind of project, each action is enabled just when it makes sense: we can only stop the HSQLDB server after an instance of it has been started for the selected project, and then we cannot start another instance, because it would listen to the same TCP port, which is already in use. In the same way, the Database Manager can only be started in client/server mode if a server was already been started. But, when there's no server running, we can run the Database Manager in stand-alone mode, where the HSQLDB engine runs in-process.

This is accomplished by the association of a session property to each project resource. Session properties are lost when the workbench is closed, so they are useful to keep any volatile status a plugin has to maintain for a given resource. They are defined programmatically, not by the plugin manifest.

There's also the need for a custom property page to be associated with each Java Project, so each one can have different connection parameters to their HSQLDB server, for example, a different user and password. Listing 5 shows the snippet of the property page extension. Enough about plugin.xml for a while, time to see some Java code!


Listing 5. plugin.xml manifest file snippet for the HSQLDB project property page
   <extension
         id="hsqldb.ui.property"
         point="org.eclipse.ui.propertyPages">
      <page
            objectClass="org.eclipse.jdt.core.IJavaProject"
            name="HSQLDB Server"
            class="hsqldb.ui.properties.DBEnginePropertyPage"
            id="hsqldb.ui.properties.DBEnginePropertyPage">
         <filter
               name="nature"
               value="hsqldb.ui.hsqldbEngine">
         </filter>
      </page>
   </extension>



Back to top


The HSQLDB Engine project nature

Adding the hsqldb.ui.hsqldbEngine nature to a Java project is straightforward, as seen in Listing 6: obtain the corresponding IProjectDescriptor and add a new element to the String array containing the ids of all natures associated with the project.

It may be counter intuitive to the reader to learn that an IJavaProject is not an IProject. The workbench and the JDT keep parallel resource hierarchies, the former representing projects, folders and files, while the other representing Java projects, packages and classes. As the "Add HSQLDB Engine nature" action is tied to Java Projects, we must first get the corresponding Workbench Project. Then we can give it a new description, including the hsqdbEngine nature.

After updating the (Workbench) project description, we customize the (Java) project CLASSPATH and direct HSQLDB to create inside it a default database named "database". Then the project is refreshed so the user can see the changes (new files and libraries).

As described in the first part of this article series, most operations involving the HSQLDB database classes themselves are static methods from class HsqldbUtil, and it will have a few new static methods added for this second part.


Listing 6. Adding the DB Engine Nature to a Java Project
IProject proj = currentProject.getProject();
IProjectDescription description = proj.getDescription();

// add the hsqldbEngine nature to the project

String[] natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);

// must prefix with plugin id!!!

newNatures[natures.length] = "hsqldb.ui.hsqldbEngine";
description.setNatureIds(newNatures);
proj.setDescription(description, null);

// add the HSQLDB classpath variable

IClasspathEntry[] rawClasspath = currentProject.getRawClasspath();
IClasspathEntry[] newRawClasspath = new IClasspathEntry[
    rawClasspath.length + 1];
System.arraycopy(rawClasspath, 0, newRawClasspath, 0,
    rawClasspath.length);
newRawClasspath[rawClasspath.length] = JavaCore.newVariableEntry(
    new Path("HSQLDB"), null, null);
currentProject.setRawClasspath(newRawClasspath, null);

// create the initial database files

IPath dbPath = proj.getLocation();
String database = dbPath.toString() + "/database";
HsqldbUtil.createEmptyHsqlDatabase(database);

// refresh project so user sees new files, libraries, etc

proj.refreshLocal(IResource.DEPTH_INFINITE, null);

Classpath variables

Besides having extra actions, a DB Engine Project needs access to the HSQLDB server classes. We already know (from the first part of this series) how to find the library hsqldb.jar that contains these classes, inside the hsqldb.core plugin. But having each project reference this library by its filesystem path is not well suited to team work. Imagine if each team member installs Eclipse in a different filesystem folder. Each one would have different paths for the library. But we don't want CVS updating each developer with the path from the last developer who did a commit.

That's the reason the second version of the HSQLDB plugin defines a new classpath variable to hold a reference to hsqldb.jar. When adding the DB Engine project nature, the project classpath is updated to include this variable, which is defined by the workbench extension point in Listing 7. There must be a class to initialize that variable; the code is shown in Listing 8.

The new classpath variable is useful also for client developers: they can simply add the variable to the Java Build Path of any client project, or to the runtime classpath of any client application. This is much easier than looking for the library jar file in the filesystem.


Listing 7. plugin.xml snippet declaring the HSQLDB classpath variable
   <extension
         point="org.eclipse.jdt.core.classpathVariableInitializer">
      <classpathVariableInitializer
            variable="HSQLDB"
            class="hsqldb.ui.classpath.HsqldbVariable">
      </classpathVariableInitializer>
   </extension>



Listing 8. Initializer class for the HSQDLDB classpath variable
public class HsqldbVariable extends ClasspathVariableInitializer {

    public HsqldbVariable() {
        super();
    }

    public void initialize(String variable) {
        // ignore the "variable" argument, since we define just one
        // classpath variable
        try {
            JavaCore.setClasspathVariable("HSQLDB", new Path(
                HsqldbUtil.getHsqldbJarPath()), null);
        }
        // can't create the classpath variable
        catch (JavaModelException e) {
            System.err.println(e);
        }
    }
}

Removing the HSQLDB Engine nature

If we provide a way to add the HSQLDB Engine project nature to a Java Project, it's natural we provide a way to remove this nature. The steps are roughly the same as for adding the nature, but we remove a nature id from the String array, instead of adding one. Listing 9 shows the complete action class code, not just the segment that changes the project description, so we can learn about some utility methods created for the second revision of the plugin.


Listing 9. Action to remove the HSQLDB Engine project nature
public class RemoveDBEngineNature implements IObjectActionDelegate {

    private IJavaProject currentProject;
    
    public RemoveDBEngineNature() {
        super();
    }

    public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    }

    public void run(IAction action) {
        try {
            IProject proj = currentProject.getProject();
            IProjectDescription description = proj.getDescription();
            // remove the hsqldbEngine nature to the project
            String[] natures = description.getNatureIds();
            String[] newNatures = new String[natures.length - 1];
            for(int i = 0; i < natures.length; i++) {
                if (!natures[i].equals("hsqldb.ui.hsqldbEngine"))
                    newNatures[i] = natures[i];
            }
            description.setNatureIds(newNatures);
            proj.setDescription(description, null);
            // refresh project so user sees changes
            proj.refreshLocal(IResource.DEPTH_INFINITE, null);
            // as both decorators share the same visibility rules, this will work
            HsqldbRunningDecorator.updateDecorators();
        }
        catch (Exception e) {
            Shell shell = new Shell();
            MessageDialog.openInformation(
                shell,
                "Hsqldb Ui Plug-in",
                "Cannot remove HSQLDB Database Engine nature:\n" +
                 ActionUtil.getStatusMessages(e));
        }
        Shell shell = new Shell();
        MessageDialog.openInformation(
            shell,
            "Hsqldb Ui Plug-in",
            "Removed HSQLDB Database Engine from this project.\n" +
            "You must manually delete database files and libraries if wanted.");
    }

    public void selectionChanged(IAction action, ISelection selection) {
        currentProject = ActionUtil.findSelectedJavaProject(selection);
    }

}

The class ActionUtil (Listing 10) contains methods for common tasks invoked from our plugin action classes. These are: finding the IJavaProject or IFile resource from the structured selection (ISelection) each action receives as argument to its selectionChanged method; and displaying a detailed error message from the many IStatus instances that can be stored inside a CoreException thrown by workbench classes. Remember, as seen in the first part of this series, each action must remember what was the last selection, because its run method gets no hint about this.

Don't worry about what's provided by the class HsqldbRunningDecorator, invoked at the end of the action run method. It will be described shortly, and deals with changing the project icons on the Package Explorer view so the user knows which ones are HSQLDB Engine projects, and which of them have running server instances.


Listing 10. ActionUtil class
public class ActionUtil {

   public static IJavaProject findSelectedJavaProject(ISelection selection) {
        IJavaProject currentProject = null;
        if (selection != null) {
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection)selection;
                Object obj = ss.getFirstElement();
                if (obj instanceof IJavaProject) {
                    currentProject = (IJavaProject)obj;
                }
            }
        }
        return currentProject;
    }
    
    public static IFile findSelectedFile(ISelection selection) {
        IFile currentFile = null;
        if (selection != null) {
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection)selection;
                // as this action is enabled only for a single selection,
                // it's enough to get the first element
                Object obj = ss.getFirstElement();
                if (obj instanceof IFile) {
                    currentFile = (IFile)obj;
                }
            }
        }
        return currentFile;
    }

    public static String getStatusMessages(Exception e) {
        String msg = e.getMessage();
        if (e instanceof CoreException) {
            CoreException ce = (CoreException)e;    
            IStatus status = ce.getStatus();
            IStatus[] children = status.getChildren();
            for (int i = 0; i < children.length; i++)
                msg += "\n" + children[i].getMessage();
            System.err.println(msg);
            ce.printStackTrace(System.err);
        }
        return msg;
    }
}



Back to top


Keeping track of running servers

Now that we can have HSQLDB Projects in the workspace, their custom actions need implementation classes. Most of the work was already done in the first part, when we learned how to start and stop an HSQLDB server and its tools. But this time there can be many active server instances, so we create a new class named HsqldbServerTracker which does four things:

  1. Starts and Stops HSQLDB server instances.
  2. Keeps a record of each running server instance.
  3. Registers a project session property when there's a running server for the corresponding database.
  4. Detects when a server instance was terminated outside the plugin actions, such as when the user clicks the stop button on the Console view, and updates the project session properties so a new server can be started.

This class is a singleton, meaning there's only one map of running server instances for all projects. It needs to be instantiated so it can listen for debug events and thus be notified of process termination.

Starting HSQLDB, revisited

In the first release of our plugin, the server was started as a new thread inside the Eclipse workbench. But, in this second release, we wish to submit the server to the JDT debugger, so there will be a launch configuration for each database, that is, for each project. This is similar to the way we started the ScriptTool in the first release, but instead of creating a brand new Java Classpath for the launch configuration, we have to use the selected project classpath, which already contains the hsqdb.jar library and the classes implementing user-defined SQL functions.

The code from HsqldbUtil.runScriptTool was refactored to create a method named launch (see Listing 11), which gets the project, main class name and command-line arguments as method parameters. The new method is then used by the rewritten runScriptTool method and by the startHsqldbServer method from class HsqldbServerTracker.


Listing 11. launch method from class HsqldUtil
protected static ILaunch launch(IJavaProject proj, String name,
                        String mainClass, String args) throws CoreException {
    ILaunchManager manager =
        DebugPlugin.getDefault().getLaunchManager();
    ILaunchConfigurationType type =
        manager.getLaunchConfigurationType(
        IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
    ILaunchConfiguration config = null;
    // if the configuration already exists, use it!
    ILaunchConfiguration[] configurations =
        manager.getLaunchConfigurations(type);
    for (int i = 0; i < configurations.length; i++) {
        if (configurations[i].getName().equals(name))
            config = configurations[i];
    }
    // else create a new one
    if (config == null) {
        ILaunchConfigurationWorkingCopy wc =
            type.newInstance(null, name);
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
            proj.getProject().getName());
        // current directory should be the project root
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
            proj.getProject().getLocation().toString());
        // use the supplied args
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
            mainClass);
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
            args);
        // saves the new config
        config = wc.doSave();
    }
    // launches the configuration
    DebugPlugin debug = DebugPlugin.getDefault();
    return config.launch(ILaunchManager.RUN_MODE, null);
}

Another change from the first release of the plugin is that the launch configurations are not recreated at each operation; instead, if a launch configuration with the project name already exists, this configuration is used. Thus the developer can customize Java VM arguments and other settings, for example, adding additional Java libraries to the classpath. Of course, it will also be possible to change the main class or remove the HSQLDB variable from the classpath, rendering the launch configuration useless, but this can be fixed easily by deleting it and re-creating using the HSQLDB plugin actions. Preserving the launch configurations also gives the developer the ability to run the server inside the JDT debugger, so he can set breakpoints on his user-defined SQL functions.

Listing 12 shows the method startHsqldbServer. It starts by loading the connection parameters from the project persistent properties, then it uses the HsqldbUtil.launch method to start the server instance, adds a debug listener so we know when the corresponding process terminates, and sets the session property, so the workbench can update the plugin actions enablement.

This second release of the plugin assumes only one database per project, and the database name is hardcoded as "database". Changing this name from a hardcoded one to a configurable one is left as an exercise for the reader.


Listing 12. method to start a HSQLDB server, from HsqldbServerTracker class
public void startHsqldbServer(IJavaProject proj) throws CoreException {
    // build server command-line
    HsqldbParams params = new HsqldbParams(proj);
    String database = proj.getProject().getLocation() + "/database";
    String args = "-database " + database + " " +
        "-port " + params.port;
    // starts the server as a Java app
    ILaunch launch = HsqldbUtil.launch(proj, proj.getElementName(),
        "org.hsqldb.Server", args);
    // saves the mapping between (server) process and project
    servers.put(launch.getProcesses()[0], proj);        
    // register a listener so we know when this process is finished
    DebugPlugin debug = DebugPlugin.getDefault();
    debug.addDebugEventListener(listener);
    // so the UI will reflect there's already a server running
    setRunning(proj, Boolean.TRUE);
}

Termination of HSQLDB server instances

The HsqldbUtil.launch method returns an ILaunch instance, from which we can get the JVM process created by launching the configuration. This process is used as the key for a Map whose stored values are Java Project (HSQLDB Engine) references.

Later on, when the debug listener registered by the server tracker is notified of a process termination (see Listing 13), it checks if the terminated process is present in the map. If it is, the session property can be removed from the project, and the project entry can be removed from the map. Thanks to the debug events fired by the workbench, the plugin actions enablement will always reflect the existence (or not) of a running server. The "running" session property is also handled by helper methods in the HsqdbServerTracker (see Listing 14).

The method that stops a running HSQLDB server does not need to update the map or the session properties, as these will be taken care by the debug event listener. It simply has to send the shutdown SQL statement to the server using a JDBC connection.


Listing 13. Debug event listener for terminated HSQLDB servers, from HsqldbServerTracker
private IDebugEventSetListener listener = new IDebugEventSetListener() {
    public void handleDebugEvents(DebugEvent[] events) {
        // if the event was a terminate...
        if (events[0].getKind() == DebugEvent.TERMINATE) {
            Object source = events[0].getSource();
            if (source instanceof IProcess) {
                // ...and the terminated process is a HSQLDB Server...
                Object proj = servers.get(source);
                if (proj != null) {
                    try {
                        //...remove it from the map and update the ui
                        servers.remove(source);
                        setRunning((IJavaProject)proj, null);
                    }
                    catch (CoreException ce) {
                        System.err.println("HsqldbServerTracker.handleDebugEvents");
                        System.err.println(ActionUtil.getStatusMessages(ce));
                    }
                }
            }
        }
    }
};



Listing 14. Helper methods for setting and getting the "running" session property, from HsqldbServerTracker
public boolean getRunning(IJavaProject javaProj) throws CoreException {
    IProject proj = javaProj.getProject();    
    Object value = proj.getSessionProperty(new QualifiedName(
        "hsqldb.ui", "running"));
    return value != null;
}

public void setRunning(IJavaProject javaProj, Boolean value)
            throws CoreException {
    if (value != null && value.equals(Boolean.FALSE))
        value = null;
    IProject proj = javaProj.getProject();    
    proj.setSessionProperty(new QualifiedName("hsqldb.ui", "running"),
        value);
    HsqldbRunningDecorator.updateDecorators(javaProj);
}

HSQLDB connection properties, revisited

The HsqldbParam class, which in the first release of the plugin was simply a C-like structure to keep HSQLDB connection parameters for the whole workbench, becomes smarter and in this second release it has the ability to load and save the parameters as persistent session properties for a given project workbench resource(see Listing 15).


Listing 15. Updated HsqldbParams class
public class HsqldbParams {
    // property names
    public static final String P_PORT = "serverPort";
    public static final String P_USER = "serverUser";
    public static final String P_PASSWD = "serverPasswd";
    
    public int port = 9001;
    public String user = "sa";
    public String passwd = "";
    
    public HsqldbParams() {}
    
    public HsqldbParams(IJavaProject javaProject) throws CoreException {
        load(javaProject);
    }
    
    public void save(IJavaProject javaProject) throws CoreException {
        IProject project = javaProject.getProject();
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PORT), Integer.toString(port));
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_USER), user);
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PASSWD), passwd);
    }
    
    public void load(IJavaProject javaProject) throws CoreException {
        IProject project = javaProject.getProject();
        String property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PORT));
        port = (property != null && property.length() > 0) ?
            Integer.parseInt(property) : port;
        property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_USER));
        user = (property != null && property.length() > 0) ? property : user;
        property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PASSWD));
        passwd = (property != null && property.length() > 0) ?
            property : passwd;
    }
}

We need to replace the HSQLDB workbench preferences page by an HSQLDB resource property page. Figure 5 shows how the page appears (basically the same as the original property page) and the code for the property page is in Listing 16. The code is a little more complex than the original preferences page, because we cannot use field editors anymore, and so we'll need to deal with "low level" SWT controls. Fortunately, it is easy to adapt the property page generated by the PDE property page wizard, and the reader won't have to dig deeper into SWT yet. The corresponding extension point was already presented in Listing 5.


Figure 5. HSQLDB Engine Project property page
HSQLDB Engine Project property page

Listing 16. Property Page implementation class
public class DBEnginePropertyPage extends PropertyPage {

    private HsqldbParams params;
    
    private Text portText;
    private Text userText;
    private Text passwdText;

    public DBEnginePropertyPage() {
        super();
    }

    private void fillControls() {
        portText.setText(Integer.toString(params.port));
        userText.setText(params.user);
        passwdText.setText(params.passwd);
    }
    
    private void getParams() {
        params = new HsqldbParams();
        try {
            params.port = Integer.parseInt(portText.getText());
        }
        catch (NumberFormatException ne) {
            // do nothing; let the default port number
        }
        params.user = userText.getText();
        params.passwd = passwdText.getText();
    }
    
    private void addControls(Composite parent) {
        Composite composite = createDefaultComposite(parent);

        Label pathLabel = new Label(composite, SWT.NONE);
        pathLabel.setText("&TCP Port:");
        portText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        GridData gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(6);
        portText.setLayoutData(gd);

        Label userLabel = new Label(composite, SWT.NONE);
        userLabel.setText("&User:");
        userText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(15);
        userText.setLayoutData(gd);

        Label passwdLabel = new Label(composite, SWT.NONE);
        passwdLabel.setText("&Password:");
        passwdText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(15);
        passwdText.setLayoutData(gd);
    }

    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        composite.setLayout(layout);
        GridData data = new GridData(GridData.FILL);
        data.grabExcessHorizontalSpace = true;
        composite.setLayoutData(data);
        addControls(composite);
        
        IJavaProject proj = (IJavaProject)getElement();
        try {
            params = new HsqldbParams(proj);
            fillControls();
        }
        catch (CoreException ce) {
            System.err.println(ActionUtil.getStatusMessages(ce));
        }
        return composite;
    }

    private Composite createDefaultComposite(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        composite.setLayout(layout);

        GridData data = new GridData();
        data.verticalAlignment = GridData.FILL;
        data.horizontalAlignment = GridData.FILL;
        composite.setLayoutData(data);

        return composite;
    }

    protected void performDefaults() {
        params = new HsqldbParams();
        fillControls();
    }
    
    public boolean performOk() {
        IJavaProject proj = (IJavaProject)getElement();
        getParams();
        try {
            params.save(proj);
        }
        catch (CoreException ce) {
            System.err.println(ActionUtil.getStatusMessages(ce));
            return false;
        }
        return true;
    }

}



Back to top


Custom Decorators

Most of the second version of the HSQLDB plugin is ready to work. After some little modifications to HsqldbUtil, to use the refactored launch method, and to get an IJavaProject as argument for starting all methods that run HSQLDB tools (so the specific connection properties can be retrieved), all desired functionality is in place, giving the developer the ability to track multiple running server instances, each one with their own database files and custom SQL functions. These changes should be trivial for the reader, who can use the previous listings as a starting point, or can download the full source code of the second version of the plugin using the links at the end of this article (see Resources).

But one important feature is currently missing -- the user needs some visual feedback about the status of each HSQLDB Engine project: first, to indicate which projects are database projects; second, to indicate which databases are running or not. Eclipse decorators are perfect for these visual clues. Figure 6 shows how a database project icon will appear, and Figure 7 shows a project with a running server. Please note that the decorator for the DB Engine Project is hiding the "J" decorator for standard Java Projects. That's the desired outcome, so the developer can quickly tell which projects are database ones (but most of the time plugin decorators should not hide standard workbench or JDT decorators).


Figure 6. DB Engine project icon
DB Engine project icon

Figure 7. Running DB Engine project icon
Running DB Engine project icon

The DB Engine decorator

The first decorator can de defined declaratively, using just the plugin.xml manifest elements (see Listing 17). That's the beauty of lightweight decorators, introduced in Eclipse 2.1. But remember the code from adding the nature (see Listing 6), if we don't fire LabelProviderChangedEvent events, the workbench views may not display the correct decorators for the changed resource. This event is actually fired by the updateDecorators from class HsqldbRunningDecorator, which represents the "other" decorator defined by the plugin. As both decorators apply to the same set of workbench resources, the xml-only decorator can borrow the method from the coded decorator as we'll see shortly.


Listing 17. xml-only decorator for DB Engine projects
   <extension
         point="org.eclipse.ui.decorators">
      <decorator
            lightweight="true"
            quadrant="TOP_RIGHT"
            location="TOP_RIGHT"
            adaptable="true"
            label="HSQLDB Engine Project"
            icon="icons/dec-dbnature.gif"
            state="true"
            id="hsqldb.ui.hsqldbEngineDecorator">
         <description>
            Flags Java Projects that had the HSQLDB Engine project
         	nature added to them.
         </description>
         <enablement>
            <and>
               <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
               </objectClass>
               <objectState
                     name="nature"
                     value="hsqldb.ui.hsqldbEngine">
               </objectState>
            </and>
         </enablement>
      </decorator>

The Running Server decorator

The reader may be tempted to try a purely declarative (xml-only) decorator to tag projects with running HSQLDB server, as shown in Listing 18. But this won't work very well, because the first time a server is started for a given project, the decorator won't be displayed. Later, if you deselect and select the project again, the icon will change to reflect its real status.

This is caused by the fact that the project is not on the list of resources affected by the decorator and so will miss the LabelProviderChangedEvent. The fix is to tie the decorator to all DB Engine projects and apply the decorator image programmatically only when the server is running.


Listing 18. Unsuccessful attempt to define a xml-only decorator for running databases
      <decorator
            lightweight="true"
            quadrant="BOTTOM_RIGHT"
            location="BOTTOM_RIGHT"
            adaptable="true"
            label="HSQLDB Running"
            state="true"
            id="hsqldb.ui.hsqldbRunningDecorator">
         <description>
            Flags HSQLDB Engine Projects which have a running
            server instance.
         </description>
         <enablement>
             <and>
                <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
                </objectClass>
                <and>
                   <objectState
                         name="nature"
                         value="hsqldb.ui.hsqldbEngine">
                   </objectState>
                   <objectState
                        name="sessionProperty"
                        value="hsqldb.ui.running">
                   </objectState>
                </and>
             </and>
         </enablement>
      </decorator>
   </extension>

Listing 19 shows the correct declaration for the second decorator, and Listing 20 the implementation class. Note the icon must be on the same file folder as the class itself, and not on the icons file folder, as the first decorator image was. Defining the decorator programmatically also has the advantage that we can send directed LabelProviderChangedEvent events, referring just to the affected project, instead of updating all DB Engine projects in the workspace. This reduces flashing on the screen and can be a big performance boost for decorators that change frequently.

The directed updateDecorators method is invoked only by the setRunning method from HsqldbServerTracker, but it could also be invoked by the add DB Engine action. It uses the "broadcast" version just to demo both alternatives.


Listing 19. Correct way to define a programmatic decorator for running databases
      <decorator
            lightweight="true"
            quadrant="BOTTOM_RIGHT"
            location="BOTTOM_RIGHT"
            adaptable="true"
            label="HSQLDB Running"
            state="true"
            class="hsqldb.ui.decorator.HsqldbRunningDecorator"
            id="hsqldb.ui.hsqldbRunningDecorator">
         <description>
            Flags HSQLDB Engine Projects which have a running server instance.
         </description>
         <enablement>
            <and>
               <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
               </objectClass>
               <objectState
                     name="nature"
                     value="hsqldb.ui.hsqldbEngine">
               </objectState>
            </and>
         </enablement>
      </decorator>
   </extension>



Listing 20. Implementation class for the running database decorator
public class HsqldbRunningDecorator
    extends LabelProvider
    implements ILightweightLabelDecorator {

    private static final ImageDescriptor runningDescriptor =
        ImageDescriptor.createFromFile (
            HsqldbRunningDecorator.class, "dec-running.gif");
            
    public void decorate(Object element, IDecoration decoration) {
        IJavaProject javaProj = (IJavaProject)element;
        try {
            if (HsqldbServerTracker.getDefault().getRunning(javaProj)) {
                decoration.addOverlay(runningDescriptor);
            }
        }
        catch (CoreException ce) {
            System.err.println(ActionUtil.getStatusMessages(ce));
        }
    }

    public static void updateDecorators(IJavaProject javaProj) {    
        IDecoratorManager dm =
            PluginUi.getDefault().getWorkbench().getDecoratorManager();
        HsqldbRunningDecorator decorator = (HsqldbRunningDecorator)
            dm.getBaseLabelProvider("hsqldb.ui.hsqldbRunningDecorator");
        decorator.fireUpdateDecorators(javaProj);
    }
    
    private void fireUpdateDecorators(IJavaProject proj) {
        // I can generate my own event to update the decorators
        // for a given resource
        final LabelProviderChangedEvent ev =
            new LabelProviderChangedEvent(this, proj); 
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                fireLabelProviderChanged(ev);
            }
        });
    }

    public static void updateDecorators() {
        // I can also let the workbench generate events to update all
        // resources affected by a decorator 
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                PluginUi.getDefault().getWorkbench().getDecoratorManager()
                    .update("hsqldb.ui.hsqldbRunningDecorator");
            }
        });
    }
        
}



Back to top


Conclusion

This article showed how to create the second release of a set of plugins that brings the HSQLDB database into the Eclipse workbench, bringing in easy ways to start and stop many instances of the database server, which can include user-defined SQL functions (implemented as Java classes), besides running SQL statements and scripts. The server can be run as a regular Java application or can be run inside the JDT debugger.

We've seen that, although the PDE helps create this plugin, providing wizards and specialized editors for some tasks, a deeper understanding of the platform and JDT core interfaces was needed, besides knowledge of the XML extension point syntax. There's still lots of room for improvement, as most tasks required manually editing the manifest file and manual creation of Java classes.

But I can assure the reader that the task would have been much more complex without the Eclipse platform and PDE, and also it would have been much harder using any other IDE extension API. Eclipse was developed from scratch to host plugins like the ones developed in this series, and the end result so far is very pleasant: nobody can tell HSQLDB was not created to be executed from inside Eclipse, except when we start the Database Manager tool, which will be the focus of the third part of this series, bringing in complete integration between the two open source software packages: HSQLDB and Eclipse.

Of course, there are many ways the plugin design could be improved, and the reader is invited to send his thoughts to the author using the email address at the end of this article.




Back to top


Download

NameSizeDownload method
hsqldbPlugin2.zipHTTP
Information about download methods


Resources



About the author

Fernando Lozano is a long-time open source zealot and Java developer, author of the book Java and GNU/Linux (available in Portuguese only). Among other pursuits, he sells services to organizations developing J2EE applications using Eclipse and other open source tools, helps maintain the Portuguese translation of the GNU Project Web site, and is part of the Linux Professional Institute Brazil council. You can reach Fernando at fernando at lozano.eti.br.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top