/ software

Hydrogen

Hydrogen is plugin for the Eclipse IDE to configure and launch one or more H2 database servers.

Motivations

I am one of those people who cannot come up with ideas for side projects to save their life. Honestly, I'm not really sure how I came up with this idea. The thought of creating an Eclipse plugin most likely came from a tech talk at work, and my team had used H2, but putting them together...?

For the first two-ish years in my professional career I worked exclusively an Eclipse RCP application. It was a fantastic experience, and it allowed me to get very familiar with SWT. A few years ago I stopped working with SWT as frequently, and realized that it would be helpful to have a project to look back on as an SWT-refresher of sorts - something to refresh my memory.

Project Structure

If you take a look at the code, you'll see the following project structure:

hydrogen
  + com.avojak.plugin.hydrogen.core
  + com.avojak.plugin.hydrogen.feature
  + com.avojak.plugin.hydrogen.lib
  + com.avojak.plugin.hydrogen.site
  + com.avojak.plugin.hydrogen.target
  + com.avojak.plugin.hydrogen.test
  + com.avojak.plugin.hydrogen.test.report

The core functionality, as the name implies, lives in the .core module. The .feature, .site, and .target modules used to define the target environment, and how the plugin will be packaged and distributed.

Obviously the .test module contains the unit tests. The .test.report module really only contains a pom.xml file which is used to aggregate code coverage data from the .core and .test modules (I'll touch on this later).

Finally, the .lib module is a fragment which distributes the H2 JAR. Even though Hydrogen installs with the H2 JAR already packaged, it can be updated later by the user.

Elements

The UI

As a plugin, there are only a few UI elements to consider:

  1. The toolbar menu
  2. The Launch Configuration dialog
  3. The preference dialog

Toolbar

toolbar

The toolbar contains a single icon with a dropdown menu. The menu emulates the Run Configuration menu that Eclipse users will be familiar with. From the menu, you can launch pre-defined server configurations, or open the menu to create new configurations.

Launch Configuration

launch-config

This is the most complicated dialog. It contains tabs for each type of server that can be run for H2: Web, TCP, and PostgreSQL. This is where the majority of the SWT work came in. Fortunately, Eclipse provides an AbstractLaunchConfigurationTab which makes input validation and basic layout and widget creation relatively simple.

Preferences

preferences

There is only a single, which lets users use a specific version of H2. As I mentioned earlier Hydrogen comes with the H2 JAR already, however I do not anticipate releasing an update to Hydrogen for every release of H2. Instead, users can simply point to a different JAR on their local file system if they would like na update.

Retrieving the version of the embedded JAR was a fun exercise. To accomplish this, I used a combination of the JarInputStream and Manifest classes to inspect the manifest file:

private static final String IMPLEMENTATION_VERSION = "Implementation-Version";

public Optional<String> getVersion(final String jarPath) {
    if (jarPath == null || jarPath.trim().isEmpty()) {
        throw new IllegalArgumentException("jarPath cannot be null or empty");
    }
    try (final JarInputStream jarStream = jarInputStreamFactory.create(jarPath)) {
        final Manifest manifest = jarStream.getManifest();
        return Optional.ofNullable(manifest.getMainAttributes().getValue(IMPLEMENTATION_VERSION));
    } catch (final FileNotFoundException e) {
        LOGGER.error("JAR file [" + jarPath + "] not found", e);
        return Optional.empty();
    } catch (final IOException e) {
        LOGGER.error("I/O error while creating stream for JAR file [" + jarPath + "]", e);
        return Optional.empty();
    }
}

To allow easy mocking during testing, I created a factory class JarInputStreamFactory which simply wraps the construction:

public JarInputStream create(final String jarPath) throws FileNotFoundException, IOException {
    return new JarInputStream(new FileInputStream(jarPath));
}

Behind the Scenes

The real meat and potatoes of Hydrogen lies within HydrogenLaunchConfigurationDelegate. This is where everything comes together and the H2 servers are launched via the H2 JAR. The class extends AbstractJavaLaunchConfigurationDelegate which provides a handful of convenient methods.

The primary method here is launch(...), which performs the following operations:

  1. Verify the JVM installation
  2. Read the Hydrogen user preferences to determine which H2 JAR to use
  3. Ensure that the H2 JAR specified is present and can be executed
  4. Create the run configuration (classpath, working directory, etc.)
  5. Build the program arguments based on what was set in the launch configuration dialog
  6. Set the JVM arguments (launch headless - we don't want the user to see an application icon or window appear when H2 is running)
  7. Run!

Build & Test

One hurdle that I faced early in development was getting accurate code coverage metrics for my unit tests. I landed on JaCoCo because it seemed easy to integrate into a Maven project. I ran into some issues due to the multi-module nature of Hydrogen, but thanks to this fantastic article, I was able to get things running quite nicely. I ended up creating a separate module specifically for an aggregated report (.test.report).

The pom.xml for the report module:

<profile>
    <id>jacoco</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <configuration>
                    <excludes>
                        <!-- Exclusions -->
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

And in the parent pom.xml:

<profile>
    <id>jacoco</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <configuration>
                    <propertyName>jacoco.agent.argLine</propertyName>
                </configuration>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.eluder.coveralls</groupId>
                    <artifactId>coveralls-maven-plugin</artifactId>
                    <version>4.3.0</version>
                    <configuration>
                        <jacocoReports>
                            <jacocoReport>${project.basedir}/${project.groupId}.hydrogen.test.report/target/site/jacoco-aggregate/jacoco.xml</jacocoReport>
                        </jacocoReports>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</profile>

As an added bonus, the JaCoCo report integrates nicely with Coveralls.

Finished Product

GitHub: https://github.com/avojak/hydrogen
Eclipse Marketplace: https://marketplace.eclipse.org/content/hydrogen

Check it out by dragging this button into Eclipse!
Drag to your running Eclipse* workspace. *Requires Eclipse Marketplace Client