Wednesday, December 31, 2008

JFace AWT Selection Binding

Goal


The goal of this entry is to embed an AWT and Swing component in an Eclipse Editor and to bind the AWT selection change events to the Eclipse Selection Service.

Create a Swing Tree and Tree Model


Because we will be combining AWT components with an Eclipse Editor, we should first create a Plug-in project that will contribute to the UI. For this example, the project can be named timezra.blog.swt_awt_selection_binding.
For the Swing components, we will use the example from the Sun Tree Tutorial as a basis for our JTree and TreeModel.

The Model Object


package timezra.blog.swt_awt_selection_binding.awt;

public class BookInfo {
public final String bookName;
public final String bookURL;

public BookInfo(final String bookName, final String bookURL) {
this.bookName = bookName;
this.bookURL = bookURL;
}

@Override
public String toString() {
return bookName;
}
}



We can use a factory method to build the JTree and populate the backing TreeModel.


package timezra.blog.swt_awt_selection_binding.awt;

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

public final class JTreeFactory {
private JTreeFactory() {
// singleton
};

public static JTree create() {
final DefaultMutableTreeNode top = new DefaultMutableTreeNode(
"The Java Series");
final DefaultMutableTreeNode category = new DefaultMutableTreeNode(
"Books for Java Programmers");
top.add(category);
category.add(new DefaultMutableTreeNode(new BookInfo(
"The Java Tutorial: A Short Course on the Basics",
"tutorial.html")));
category.add(new DefaultMutableTreeNode(new BookInfo(
"The Java Tutorial Continued: The Rest of the JDK",
"tutorialcont.html")));
category.add(new DefaultMutableTreeNode(new BookInfo(
"The JFC Swing Tutorial: A Guide to Constructing GUIs",
"swingtutorial.html")));
category.add(new DefaultMutableTreeNode(new BookInfo(
"Effective Java Programming Language Guide", "bloch.html")));
category.add(new DefaultMutableTreeNode(new BookInfo(
"The Java Programming Language", "arnold.html")));
category.add(new DefaultMutableTreeNode(new BookInfo(
"The Java Developers Almanac", "chan.html")));
return new JTree(new DefaultTreeModel(top));
}
}



Create an Eclipse Editor


We can create a very basic Eclipse Editor and contribute it through the plugin.xml. Here, the editor is opened for any file with the .book extension.
The Editor Extension

In order to bind the Swing component to the SWT Composite, we can use the SWT_AWT Bridge. We will wrap the JTree with an AWT Panel and place that Panel in the Frame returned by the SWT_AWT factory. Also, for this example, we will ensure that all the TreeNodes are expanded in the JTree.


package timezra.blog.swt_awt_selection_binding.editors;

import java.awt.Frame;
import java.awt.Panel;
import javax.swing.JTree;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import timezra.blog.swt_awt_selection_binding.awt.JTreeFactory;

public class JTreeEditor extends EditorPart {

@Override
public void doSave(final IProgressMonitor monitor) {
// no-op
}

@Override
public void doSaveAs() {
// no-op
}

@Override
public void init(final IEditorSite site, final IEditorInput input)
throws PartInitException {
setSite(site);
setInput(input);
}

@Override
public boolean isDirty() {
return false;
}

@Override
public boolean isSaveAsAllowed() {
return false;
}

@Override
public void setFocus() {
// no-op
}

@Override
public void createPartControl(final Composite parent) {
final Panel panel = new Panel();
final JTree jTree = JTreeFactory.create();
panel.add(jTree);
for (int i = 0; i < jTree.getRowCount(); i++) {
jTree.expandRow(i);
}
final Composite container = new Composite(parent, SWT.EMBEDDED);
final Frame frame = SWT_AWT.new_Frame(container);
frame.add(panel);
}
}



If we run with a new Eclipse launch configuration, create a default project and a stub .book file we will now see the Editor with the embedded AWT and Swing components.
The JTree embedded in an Eclipse Editor

Create a SelectionProvider


We are ready to create a JTreeSelectionProvider which listens for AWT selection events and broadcasts those events to the Eclipse Workbench. Note that the TreeSelectionAdapter is responsible for ensuring that SelectionChangedEvents are dispatched on the SWT Display thread; otherwise, the events will be dispatched on the AWT Event Thread.


package timezra.blog.swt_awt_selection_binding.editors;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;

class JTreeSelectionProvider implements ISelectionProvider {

private final Map<ISelectionChangedListener, TreeSelectionListener> swt2Swing;
private final JTree tree;

JTreeSelectionProvider(final JTree tree) {
this.tree = tree;
swt2Swing = new HashMap<ISelectionChangedListener, TreeSelectionListener>();
}

public void addSelectionChangedListener(
final ISelectionChangedListener listener) {
final TreeSelectionAdapter adapter = new TreeSelectionAdapter(listener);
swt2Swing.put(listener, adapter);
tree.addTreeSelectionListener(adapter);
}

public void removeSelectionChangedListener(
final ISelectionChangedListener listener) {
tree.removeTreeSelectionListener(swt2Swing.get(listener));
}

public ISelection getSelection() {
final TreePath[] paths = tree.getSelectionPaths();
if (paths == null) {
return StructuredSelection.EMPTY;
}
final List<Object> selections = new ArrayList<Object>();
for (final TreePath path : paths) {
selections.add(((DefaultMutableTreeNode) path.getLastPathComponent())
.getUserObject());
}
return new StructuredSelection(selections);
}

public void setSelection(final ISelection selection) {
// not yet implemented
}

private final class TreeSelectionAdapter implements TreeSelectionListener {

private final ISelectionChangedListener listener;

TreeSelectionAdapter(final ISelectionChangedListener listener) {
this.listener = listener;
}

public void valueChanged(final TreeSelectionEvent e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
listener.selectionChanged(new SelectionChangedEvent(
JTreeSelectionProvider.this, getSelection()));
}
});
}
}
}



We may then register this ISelectionProvider with our Eclipse Editor Site.


public class JTreeEditor extends EditorPart {
....
public void createPartControl(final Composite parent) {
....
getSite().setSelectionProvider(new JTreeSelectionProvider(jTree));
}
....
}



Create a Command


To test whether the provider properly propagates selection events, we can create a new Command. For our test, the command should only be enabled if a single BookInfo is selected. All the command contribution and enablement information can be configured through extension points.
Here, the command handler is enabled when exactly one instance of BookInfo is selected.
The Command Contribution and Enablement Configuration.

The command handler implementation displays the Book information in a message dialog.


package timezra.blog.swt_awt_selection_binding.handlers;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.HandlerUtil;
import timezra.blog.swt_awt_selection_binding.awt.BookInfo;

public class BookInfoSelectionHandler extends AbstractHandler {
public Object execute(final ExecutionEvent event) throws ExecutionException {
final IWorkbenchWindow window = HandlerUtil
.getActiveWorkbenchWindowChecked(event);
final BookInfo book = (BookInfo) ((IStructuredSelection) HandlerUtil
.getCurrentSelectionChecked(event)).getFirstElement();
MessageDialog.openInformation(window.getShell(), "Book Info", book
.toString());
return null;
}
}



If we run with our SWT_AWT launch configuration again, this time, we will notice the new command contribution and we will see that the selection of different TreeNodes affects the behavior of the command contribution.
When no BookInfo is selected, the command is disabled.
When no BookInfo is selected, the command is disabled.

When exactly one BookInfo is selected, the command is enabled.
When exactly one BookInfo is selected, the command is enabled.

The BookInfo is displayed in a message dialog.
The BookInfo is displayed in a message dialog.

Friday, December 12, 2008

JMeter in Eclipse and Hudson

Goal


The intention of this tutorial is to describe the creation of an Axis2 WebService in Eclipse along with the setup of JMeter for functionally testing that WebService. The user will go through the steps of manually testing the WebService in a single instance, then will use JMeter to automate multiple runs of the test case, and finally will use Hudson to automate the running of the JMeter Test Plan with Ant.

Create an Axis2 WebService



We want to deploy a new WebService in Eclipse using Axis2 and Tomcat. Here we can create an Axis2 project called timezra.blog.axis along with a simple service implementation that returns the reverse of an input String.




package timezra.blog.axis;

public class Reverser {
public String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}



An Axis2 project with the service implementation.

Manually Test The WebService


After deploying the WebService, we can use a tool such as SoapUI for Eclipse to create a WebService request.


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:axis="http://axis.blog.timezra">
<soapenv:Header/>
<soapenv:Body>
<axis:reverse>
<axis:input>?</axis:input>
</axis:reverse>
</soapenv:Body>
</soapenv:Envelope>



A JMeter Project in Eclipse


JMeter is not well-integrated into Eclipse, but we can still run it from the Eclipse environment. First, we will create a New -> Project... -> Java Project called timezra.blog.jmeter.tests. We will need to set Properties -> Java Build Path -> Source -> Default output folder to a directory other than bin. JMeter houses its executables in the bin, but this directory is cleaned by the Eclipse builder when it re-compiles any project source code. Now we can download the JMeter binary distribution and unpack the contents directly into the timezra.blog.jmeter.tests project. For our particular JMeter tests, we will also want to put the Beanshell, Junit 4, and java mail jars into the lib, lib/junit and lib directories, respectively. Beanshell will be used for writing Beanshell Assertions and Post-processors in our test cases. JUnit 4 will provide access to JUnit assertions and Hamcrest Matchers in Beanshell. Java mail is necessary for running WebService Samplers.

I tend to prefer using a custom Main runner to using the JMeter bat and shell scripts from inside Eclipse. A simple runner only contains a few lines of code.


package timezra.blog.jmeter.tests;

import java.io.File;
import java.io.IOException;

public class Main {
public static void main(final String[] args) throws IOException {
System.setProperty("user.dir", new File("bin").getCanonicalPath());
org.apache.jmeter.NewDriver.main(args);
}
}



The JMeter Eclipse Project With Main Runner
The JMeter Eclipse Project.

Run JMeter From Eclipse


Now that we have a main runner, we can go to src -> Run As -> Java Application. We may need to adjust the heap settings of the launch configuration to something along the lines of -Xms40m -Xmx512m as our Test Plans become more robust.
Suppose we want to run our WebService using input from a CSV file. Let's save our initial Test plan as /timezra.blog.jmeter.tests/tests/ReverseTest.jmx. We will also want to save a data file called /timezra.blog.jmeter.tests/tests/ReverseTestData.csv, and in here we can include a few test messages for the WebService. Below is a snippet from a test file with 500 lines that can easily be generated:

This is input 1 to the WebService.
This is input 2 to the WebService.
This is input 3 to the WebService.
....
This is input 500 to the WebService.

From the JMeter test, we can use each line of input by creating a Test Plan -> Add -> Config Element -> CSV Data Set Config. The file name is relative to the Test Plan. Our variable can be called input. All threads can share the file.
The CSV Data Set Config

We can now create the Test Plan -> Add -> Thread Group that will run the WebService test. Our number of threads can be set to 10 and the loop count can be set to 50, i.e., there will be 10 concurrent requests in 50 batches.
The Thread Group configuration.

To run the WebService Request we will create a Thread Group -> Add -> Sampler -> WebService(SOAP) Request. Here we can set the WSDL URL (http://localhost:8080/timezra.blog.axis/services/Reverser?wsdl), Load WSDL, Configure and paste the SOAP body from our previous manual test into the SOAP/XML-RPC Data area. To view the SOAP response, we also want to ensure that Read SOAP Response is checked.
The WebService Sampler configuration.

To interpret the response from the WebService request we can include a WebService(SOAP) Request -> Add -> Post Processors -> Regular Expression Extractor and we can assign the contents of the ns:return tag to a variable output.
The Regular Expression Extractor Configuration.

To verify the output, we can create a WebService(SOAP) Request -> Add -> Assertions -> BeanShell Assertion that compares the reversed WebService input with the WebService output.


String input = vars.get("input");
String output = vars.get("output");
String reversedInput = new StringBuilder(input).reverse().toString();
org.junit.Assert.assertEquals(reversedInput, output);



The BeanShell Assertion.
The BeanShell Assertion Configuration.

Finally, we can add listeners for reporting on the test run output. Here, we will create a Thread Group -> Add -> Listeners -> View Results Tree and Thread Group -> Add -> Listeners -> Graph Results.
The View Results Tree.
The Tree depicts request and assertion results and specific WebService responses.

The Graphed Results.
The Graph displays average, median, min and max request times for all thread runs.

Run JMeter From Ant


Now that we have a manual JMeter test, we will automate the process. We can create an Ant file for running the tests from our Continuous Integration server. This build script will also transform the JMeter result JTL file to a human-readable HTML file and place it into an expected build artifacts directory. Fortunately, JMeter provides XSL stylesheets for this transformation in the project extras directory.


<project name="JMeter-Runner" default="run-jmeter-tests">
<property name="jmeter.home" location="." />
<property name="jmeter.tests" location="tests" />
<property name="jmeter.results.dir" location="jmeter-results" />
<property name="jmeter.results.jtl" location="${jmeter.results.dir}/jmeter-results.jtl" />
<property name="jmeter.results.html" location="${jmeter.results.dir}/jmeter-results.html" />
<property name="jmeter.results.xsl" location="extras/jmeter-results-detail-report_21.xsl" />

<taskdef name="jmeter" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask">
<classpath>
<fileset dir="${jmeter.home}/extras">
<include name="ant-jmeter-*.jar" />
</fileset>
</classpath>
</taskdef>

<target name="init">
<mkdir dir="${jmeter.results.dir}" />
</target>

<target name="clean">
<delete failonerror="false">
<fileset dir="${jmeter.results.dir}">
<include name="**/*" />
</fileset>
</delete>
</target>

<target name="reformat-report">
<xslt in="${jmeter.results.jtl}" out="${jmeter.results.html}" style="${jmeter.results.xsl}" />
</target>

<target name="run-jmeter-tests" depends="init, clean">
<jmeter jmeterhome="${jmeter.home}" resultlog="${jmeter.results.jtl}" failureproperty="failure-property">
<property name="jmeter.save.saveservice.output_format" value="xml" />
<property name="jmeter.save.saveservice.response_data.on_error" value="true" />
<testplans dir="${jmeter.tests}" includes="*.jmx" />
</jmeter>
<antcall target="reformat-report" />
<fail>
<condition>
<equals arg1="${failure-property}" arg2="true" />
</condition>
</fail>
</target>
</project>



JMeter In Hudson


Finally, we can setup a Hudson build on Tomcat.
Because we do not have a source control manager, we will point Hudson to a Custom Workspace (under Advanced Project Options), configure the builder to Invoke Ant, and archive the JMeter HTML result report artifact.
The Hudson Build Configuration.

The HTML artifact will now appear on the build results page.
The Build Result Page.

The JMeter Detail Report.
The JMeter Detail Report can be opened in a browser.