Testing a Save As Dialog in Java using Swing

Exclusive offer: get 50% off this eBook here
Swing Extreme Testing

Swing Extreme Testing — Save 50%

The Extreme approach to complete Java application testing

$23.99    $12.00
by Lindsay Peters Tim Lavers | January 2009 | Java Open Source

In this article by Tim Lavers and Lindsay Peters, we will be studying in detail the test for an extremely simple user interface component. It will involve UI Wrappers for components as a way of safely and easily manipulating them in tests and specific techniques for reading the state of our user interfaces. Although the component that we'll be testing in this article is simple, it will still allow us to introduce a few specific techniques for testing Swing user interfaces. It will also provide us with an excellent opportunity for showing the basic infrastructure that needs to be built into these kinds of tests.

In this article, we will use an example from our demonstration application, Ikon Do It. The code from this article is in the packages jet.ikonmaker and jet.ikonmaker.test. Click here to download the code.

The Ikon Do It 'Save as' Dialog

The 'Ikon Do It' application has a Save as function that allows the icon on which we are currently working to be saved with another name. Activating the Save as button displays a very simple dialog for entering the new name. The following figure shows the 'Ikon Do It' Save as dialog.

Testing a Save As Dialog in Java using Swing

Not all values are allowed as possible new names. Certain characters (such as '*') are prohibited, as are names that are already used.

In order to make testing easy, we implemented the dialog as a public class called SaveAsDialog, rather than as an inner class of the main user interface component. We might normally balk at giving such a trivial component its own class, but it is easier to test when written this way and it makes a good example. Also, once a simple version of this dialog is working and tested, it is possible to think of enhancements that would definitely make it too complex to be an inner class. For example, there could be a small status area that explains why a name is not allowed (the current implementation just disables the Ok button when an illegal name is entered, which is not very user-friendly).

The API for SaveAsDialog is as follows. Names of icons are represented by IkonName instances. A SaveAsDialog is created with a list of existing IkonNames. It is shown with a show() method that blocks until either Ok or Cancel is activated. If Ok is pressed, the value entered can be retrieved using the name() method. Here then are the public methods:

public class SaveAsDialog {
public SaveAsDialog( JFrame owningFrame,
SortedSet<IkonName> existingNames ) { ... }
/**
* Show the dialog, blocking until ok or cancel is activated.
*/
public void show() { ... }
/**
* The most recently entered name.
*/
public IkonName name() { ... }
/**
* Returns true if the dialog was cancelled.
*/
public boolean wasCancelled() { ... }
}

Note that SaveAsDialog does not extend JDialog or JFrame, but will use delegation. Also note that the constructor of SaveAsDialog does not have parameters that would couple it to the rest of the system. This means a handler interface is not required in order to make this simple class testable.

The main class uses SaveAsDialog as follows:

private void saveAs() {
SaveAsDialog sad = new SaveAsDialog( frame,
store.storedIkonNames() );
sad.show();
if (!sad.wasCancelled()) {
//Create a copy with the new name.
IkonName newName = sad.name();
Ikon saveAsIkon = ikon.copy( newName );
//Save and then load the new ikon.
store.saveNewIkon( saveAsIkon );
loadIkon( newName );
}
}

Outline of the Unit Test

The things we want to test are:

Initial settings:

  • The text field is empty.
  • The text field is a sensible size.
  • The Ok button is disabled.
  • The Cancel button is enabled.
  • The dialog is a sensible size.

Usability:

  • The Escape key cancels the dialog.
  • The Enter key activates the Ok button.
  • The mnemonics for Ok and Cancel work.

Correctness. The Ok button is disabled if the entered name:

  • Contains characters such as '*', '', '/'.
  • Is just white-space.
  • Is one already being used.

API test: unit tests for each of the public methods.

As with most unit tests, our test class has an init() method for getting an object into a known state, and a cleanup() method called at the end of each test. The instance variables are:

  • A JFrame and a set of IkonNames from which the SaveAsDialog can be constructed
  • A SaveAsDialog, which is the object under test.
  • A UserStrings and a UISaveAsDialog (listed later on) for manipulating the SaveAsDialog with keystrokes.
  • A ShowerThread, which is a Thread for showing the SaveAsDialog. This is listed later on.

The outline of the unit test is:

public class SaveAsDialogTest {
private JFrame frame;
private SaveAsDialog sad;
private IkonMakerUserStrings =
IkonMakerUserStrings.instance();
private SortedSet<IkonName> names;
private UISaveAsDialog ui;
private Shower shower;
...
private void init() {
...
}
private void cleanup() {
...
}
private class ShowerThread extends Thread {
...
}
}

UI Helper Methods

A lot of the work in this unit test will be done by the static methods in our helper class, UI. Some of these are isEnabled(), runInEventThread(), and findNamedComponent(). The new methods are listed now, according to their function.

Dialogs

If a dialog is showing, we can search for a dialog by name, get its size, and read its title:

public final class UI {
...
/**
* Safely read the showing state of the given window.
*/
public static boolean isShowing( final Window window ) {
final boolean[] resultHolder = new boolean[]{false};
runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = window.isShowing();
}
} );
return resultHolder[0];
}
/**
* The first found dialog that has the given name and
* is showing (though the owning frame need not be showing).
*/
public static Dialog findNamedDialog( String name ) {
Frame[] allFrames = Frame.getFrames();
for (Frame allFrame : allFrames) {
Window[] subWindows = allFrame.getOwnedWindows();
for (Window subWindow : subWindows) {
if (subWindow instanceof Dialog) {
Dialog d = (Dialog) subWindow;
if (name.equals( d.getName() )
&& d.isShowing()) {
return (Dialog) subWindow;
}
}
}
}
return null;
}
/**
* Safely read the size of the given component.
*/
public static Dimension getSize( final Component component ) {
final Dimension[] resultHolder = new Dimension[]{null};
runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = component.getSize();
}
} );
return resultHolder[0];
}
/**
* Safely read the title of the given dialog.
*/
public static String getTitle( final Dialog dialog ) {
final String[] resultHolder = new String[]{null};
runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = dialog.getTitle();
}
} );
return resultHolder[0];
} ...
}

Getting the Text of a Text Field

The method is getText(), and there is a variant to retrieve just the selected text:

//... from UI
/**
* Safely read the text of the given text component.
*/
public static String getText( JTextComponent textComponent ) {
return getTextImpl( textComponent, true );
}
/**
* Safely read the selected text of the given text component.
*/
public static String getSelectedText(
JTextComponent textComponent ) {
return getTextImpl( textComponent, false );
}
private static String getTextImpl(
final JTextComponent textComponent,
final boolean allText ) {
final String[] resultHolder = new String[]{null};
runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = allText ? textComponent.getText() :
textComponent.getSelectedText();
}
} );
return resultHolder[0];
}

Frame Disposal

In a lot of our unit tests, we will want to dispose of any dialogs or frames that are still showing at the end of a test. This method is brutal but effective:

//... from UI
public static void disposeOfAllFrames() {
Runnable runnable = new Runnable() {
public void run() {
Frame[] allFrames = Frame.getFrames();
for (Frame allFrame : allFrames) {
allFrame.dispose();
}
}
};
runInEventThread( runnable );
}

Unit Test Infrastructure

Having seen the broad outline of the test class and the UI methods needed, we can look closely at the implementation of the test. We'll start with the UI Wrapper class and the init() and cleanup() methods.

The UISaveAsDialog Class

UISaveAsDialog has methods for entering a name and for accessing the dialog, buttons, and text field. The data entry methods use a Cyborg, while the component accessor methods use UI:

public class UISaveAsDialog {
Cyborg robot = new Cyborg();
private IkonMakerUserStrings us =
IkonMakerUserStrings.instance();
protected Dialog namedDialog;
public UISaveAsDialog() {
namedDialog = UI.findNamedDialog(
SaveAsDialog.DIALOG_NAME );
Waiting.waitFor( new Waiting.ItHappened() {
public boolean itHappened() {
return nameField().hasFocus();
}
}, 1000 );
}
public JButton okButton() {
return (JButton) UI.findNamedComponent(
IkonMakerUserStrings.OK );
}
public Dialog dialog() {
return namedDialog;
}
public JButton cancelButton() {
return (JButton) UI.findNamedComponent(
IkonMakerUserStrings.CANCEL );
}
public JTextField nameField() {
return (JTextField) UI.findNamedComponent(
IkonMakerUserStrings.NAME );
}
public void saveAs( String newName ) {
enterName( newName );
robot.enter();
}
public void enterName( String newName ) {
robot.selectAllText();
robot.type( newName );
}
public void ok() {
robot.altChar( us.mnemonic( IkonMakerUserStrings.OK ) );
}
public void cancel() {
robot.altChar( us.mnemonic( IkonMakerUserStrings.CANCEL ) );
}
}

A point to note here is the code in the constructor that waits for the name text field to have focus. This is necessary because the inner workings of Swing set the focus within a shown modal dialog as a separate event. That is, we can't assume that showing the dialog and setting the focus within it happen within a single atomic event. Apart from this wrinkle, all of the methods of UISaveDialog are straightforward applications of UI methods.

The ShowerThread Class

Since SaveAsDialog.show() blocks, we cannot call this from our main thread; instead we spawn a new thread. This thread could just be an anonymous inner class in the init() method:

private void init() {
//Not really what we do...
//setup...then launch a thread to show the dialog.
//Start a thread to show the dialog (it is modal).
new Thread( "SaveAsDialogShower" ) {
public void run() {
sad = new SaveAsDialog( frame, names );
sad.show();
}
}.start();
//Now wait for the dialog to show...
}

The problem with this approach is that it does not allow us to investigate the state of the Thread that called the show() method. We want to write tests that check that this thread is blocked while the dialog is showing.

Our solution is a simple inner class:

private class ShowerThread extends Thread {
private boolean isAwakened;
public ShowerThread() {
super( "Shower" );
setDaemon( true );
}
public void run() {
Runnable runnable = new Runnable() {
public void run() {
sad.show();
}
};
UI.runInEventThread( runnable );
isAwakened = true;
}
public boolean isAwakened() {
return Waiting.waitFor( new Waiting.ItHappened() {
public boolean itHappened() {
return isAwakened;
}
}, 1000 );
}
}

The method of most interest here is isAwakened(), which waits for up to one second for the awake flag to have been set, this uses a class, Waiting. Another point of interest is that we've given our new thread a name (by the call super("Shower") in the constructor). It's really useful to give each thread we create a name.

The init() Method

The job of the init() method is to create and show the SaveAsDialog instance so that it can be tested:

private void init() {
//Note 1
names = new TreeSet<IkonName>();
names.add( new IkonName( "Albus" ) );
names.add( new IkonName( "Minerva" ) );
names.add( new IkonName( "Severus" ) );
names.add( new IkonName( "Alastair" ) );
//Note 2
Runnable creator = new Runnable() {
public void run() {
frame = new JFrame( "SaveAsDialogTest" );
frame.setVisible( true );
sad = new SaveAsDialog( frame, names );
}
};
UI.runInEventThread( creator );
//Note 3
//Start a thread to show the dialog (it is modal).
shower = new ShowerThread();
shower.start();
//Note 4
//Wait for the dialog to be showing.
Waiting.waitFor( new Waiting.ItHappened() {
public boolean itHappened() {
return UI.findNamedFrame(
SaveAsDialog.DIALOG_NAME ) != null;
}
}, 1000 );
//Note 5
ui = new UISaveAsDialog();
}

Now let's look at some of the key points in this code.

Note 1: In this block of code we create a set of IkonNames with which our SaveAsDialog can be created.

Note 2: It's convenient to create and show the owning frame and create the SaveAsDialog in a single Runnable. An alternative would be to create and show the frame with a UI call and use the Runnable just for creating the SaveAsDialog.

Note 3: Here we start our Shower, which will call the blocking show() method of SaveAsDialog from the event thread.

Note 4: Having called show() via the event dispatch thread from our Shower thread, we need to wait for the dialog to actually be showing on the screen. The way we do this is to search for a dialog that is on the screen and has the correct name.

Note 5: Once the SaveAsDialog is showing, we can create our UI Wrapper for it.

The cleanup() Method

The cleanup() method closes all frames in a thread-safe manner:

private void cleanup() {
UI.disposeOfAllFrames();
}
Swing Extreme Testing The Extreme approach to complete Java application testing
Published: June 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

The Unit Tests

We've now done all the hard work of building an infrastructure that will make our tests very simple to write. Let's now look at these tests.

The Constructor Test

A freshly constructed SaveAsDialog should be in a known state, and we need to check the things we listed at the start of this article.

public boolean constructorTest() {
//Note 1
init();
//Note 2
//Check the title.
assert UI.getTitle( ui.dialog() ).equals(
us.label( IkonMakerUserStrings.SAVE_AS ) );
//Note 3
//Check the size.
Dimension size = UI.getSize( ui.dialog() );
assert size.width > 60;
assert size.width < 260;
assert size.height > 20;
assert size.height < 200;
//Note 4
//Name field initially empty.
assert UI.getText( ui.nameField() ).equals( "" );
//Name field a sensible size.
Dimension nameFieldSize = UI.getSize( ui.nameField() );
assert nameFieldSize.width > 60;
assert nameFieldSize.width < 260;
assert nameFieldSize.height > 15;
assert nameFieldSize.height < 25;
//Ok not enabled.
assert !UI.isEnabled( ui.okButton() );
//Cancel enabled.
assert UI.isEnabled( ui.cancelButton() );
//Type in some text and check that the ok button is now enabled.
ui.robot.type( "text" );
assert UI.isEnabled( ui.okButton() );
cleanup();
return true;
}

Let's now look at the noteworthy parts of this code.

Note 1: In accordance with the rules for GrandTestAuto, the test is a public method, returns a boolean, and has name ending with "Test". As with all of the tests, we begin with the init() method that creates and shows the dialog. After the body of the test, we call cleanup() and return true. If problems are found, they cause an assert exception.

Note 2: The UI Wrapper gives us the dialog, and from this we can check the title. It is important that we are using the UI class to get the title of the dialog in a thread-safe manner.

Note 3: Here we are checking the size of dialog is reasonable. The actual allowed upper and lower bounds on height and width were found simply by trial and error.

Note 4: Although the UI Wrapper gives us access to the name field and the buttons, we must not interrogate them directly. Rather, we use our UI methods to investigate them in a thread-safe manner.

The wasCancelled() Test

The first of our API tests is to check the wasCancelled() method. We will basically do three investigations. The first test will call wasCancelled() before the dialog has been cancelled. The second test will cancel the dialog and then call the method. In the third test we will enter a name, cancel the dialog, and then call wasCancelled().

There will be a subtlety in the test relating to the way that the Cancel button operates. The button is created in the constructor of the SaveAsDialog:

//From the constructor of SaveAsDialog
...
AbstractAction cancelAction = new AbstractAction() {
public void actionPerformed( ActionEvent a ) {
wasCancelled = true;
dialog.dispose();
}
};
JButton cancelButton = us.createJButton( cancelAction,
IkonMakerUserStrings.CANCEL );
buttonBox.add( Box.createHorizontalStrut( 5 ) );
buttonBox.add( cancelButton );
...

The action associated with the button sets the wasCancelled instance variable of the SaveAsDialog. The wasCancelled() method simply returns this variable:

From SaveAsDialog
public boolean wasCancelled() {
return wasCancelled;
}

It follows that the wasCancelled() method is not thread-safe because the value it returns is set in the event thread. Therefore, in our test, we need to call this method from the event thread. To do this, we put a helper method into our test class:

//From SaveAsDialogTest
private boolean wasCancelled() {
final boolean[] resultHolder = new boolean[1];
UI.runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = sad.wasCancelled();
}
} );
return resultHolder[0];
}

Our wasCancelledTest() then is:

public boolean wasCancelledTest() {
//When the ok button has been pressed.
init();
assert !wasCancelled();
ui.saveAs( "remus" );
assert !UI.isShowing( ui.dialog() );
assert !wasCancelled();
cleanup();
//Cancel before a name has been entered.
init();
ui.cancel();
assert !UI.isShowing( ui.dialog() );
assert wasCancelled();
cleanup();
//Cancel after a name has been entered.
init();
ui.robot.type( "remus" );
ui.cancel();
assert !UI.isShowing( ui.dialog() );
assert wasCancelled();
cleanup();
return true;
}

There are three code blocks in the test, corresponding to the cases discussed above, with each block of code being very simple and making use of the wasCancelled() helper method.

Writing code like the wasCancelled() method is pretty tiresome but is essential for solid tests. In fact, it's so important and so easily overlooked that we include it as a guideline:

Extreme Testing Guideline: Any variable in a user interface or handler that is set from the event thread needs to be read in a thread-safe manner.

The name() Test

Like the wasCancelled() method, the name() method is not thread-safe, so our test class needs another boilerplate helper method:

//From SaveAsDialogTest
private IkonName enteredName() {
final IkonName[] resultHolder = new IkonName[1];
UI.runInEventThread( new Runnable() {
public void run() {
resultHolder[0] = sad.name();
}
} );
return resultHolder[0];
}

Using this, we can write our nameTest():

public boolean nameTest() {
init();
//Note 1
assert enteredName() == null;
//Note 2
ui.robot.type( "remus" );
assert enteredName().equals( new IkonName( "remus" ) );
//Note 3
ui.ok();
assert enteredName().equals( new IkonName( "remus" ) );
cleanup();
return true;
}

The main points of this test are as follows.

Note 1: Here we simply check that with no value entered into the text field, the method returns null. This could have gone into the constructor test.

Note 2: UISaveAsDialog has an enterName() method that types in the name and then presses Enter. In this test we want to type in a name, but not yet activate Ok by pressing Enter. So we use the Cyborg in the UISaveDialog to just type the name, and then we check the value of name(). This part of the test helps to define the method SaveAsDialog.name() by establishing that a value is returned even when the Ok button has not been activated.

Note 3: Here we are just testing that activating the Ok button has no effect on the value of name(). Later, we will also test whether the Ok button disposes the dialog.

It would be tempting to write some reflective method that made methods like name(), wasCancelled(), and enteredName() one-liners. However, that would make these examples much harder to understand. A bigger problem, though, is that we would lose compile-time checking: reflection breaks at runtime when we rename methods.

The show() Test

Our tests have used the show() method because it is used in init(). So we can be sure that show() actually does bring up the SaveAsDialog user interface. What we will check in showTest() is that the show() method blocks the calling thread.

public boolean showTest() {
init();
assert !shower.isAwakened();
ui.cancel();
assert shower.isAwakened();
cleanup();
init();
assert !shower.isAwakened();
ui.saveAs( "ikon" );
assert shower.isAwakened();
cleanup();
return true;
}

In the first sub-test, we check that cancellation of the SaveAsDialog wakes the launching thread. In the second sub-test, we check that activation of the Ok button wakes the launching thread.

The Data Validation Test

The Ok button of the SaveAsDialog should only be enabled if the name that has been entered is valid. A name can be invalid if it contains an illegal character, or if it has already been used.

To test this behavior, we type in an invalid name, check that the Ok button is not enabled, then type in a valid name and test that it now is enabled:

ui.enterName( "*" );
assert !UI.isEnabled( ui.okButton() );
ui.enterName( "remus");
assert UI.isEnabled( ui.okButton() );

Our validateDataTest() started with a single block of code like that above. This block of code was copied and varied with different invalid strings:

public boolean validateDataTest() {
//First check names that have illegal characters.
init();
ui.robot.type( " " );
assert !UI.isEnabled( ui.okButton() );
ui.enterName( "remus");
assert UI.isEnabled( ui.okButton() );
ui.enterName( "*" );
assert !UI.isEnabled( ui.okButton() );
ui.enterName( "remus");
assert UI.isEnabled( ui.okButton() );
...
//Seven more blocks just like these.
...
cleanup();
return true;
}

Later on, this was refactored to:

public boolean validateDataTest() {
init();
//First check names that have illegal characters.
checkOkButton( " " );
checkOkButton( "*" );
checkOkButton( "/" );
checkOkButton( "" );
//Now names that are already there.
checkOkButton( "albus" );
checkOkButton( "Albus" );
checkOkButton( "ALBUS" );
checkOkButton( "MINERVA" );
cleanup();
return true;
}
private void checkOkButton( String name ) {
ui.enterName( name );
assert !UI.isEnabled( ui.okButton() );
ui.enterName( "remus" );
assert UI.isEnabled( ui.okButton() );
}

The refactored code is much more readable, contains fewer lines, and does not contain the useless repeated test for the illegal string "*" that the original does. This illustrates a good point about writing test code. Because a lot of tests involve testing the state of an object against various simple inputs, it is very easy for such code to end up being unreadable and horribly repetitive. We should always be looking to refactor such code. Not only will this make the tests easier to maintain, it also makes the code more interesting to write. We should apply the same quality standards to our test code that we do to our production code.

The Usability Test

Typically, a simple dialog should be able to be cancelled with the Escape key, and the Enter key should activate the Ok button. In this test, we check these usability requirements and also check that tabbing to the buttons and activating them with the space key works as expected.

public boolean usabilityTest() {
//Check that 'escape' cancels.
init();
ui.robot.escape();
assert !UI.isShowing( ui.dialog() );
assert wasCancelled();
cleanup();
//Check activating the cancel button when it has focus.
init();
ui.robot.tab();//Only one tab needed as ok is not enabled.
ui.robot.activateFocussedButton();
assert !UI.isShowing( ui.dialog() );
assert wasCancelled();
cleanup();
//Check that 'enter' is like 'ok'.
init();
ui.robot.type( "remus" );
ui.robot.enter();
assert !UI.isShowing( ui.dialog() );
assert !wasCancelled();
assert enteredName().equals( new IkonName( "remus" ) );
cleanup();
//Check activating the ok button when it has focus.
init();
ui.robot.type( "remus" );
ui.robot.tab();
ui.robot.activateFocussedButton();
assert !UI.isShowing( ui.dialog() );
assert !wasCancelled();
assert enteredName().equals( new IkonName( "remus" ) );
cleanup();
return true;
}

Summary

This article has given us all the principles we need to write solid, automated, and fairly painless tests for our user interfaces. The key points are:

  • We should write a UI Wrapper class for the class we are testing and use it to manipulate the test objects.
  • All creation and setup of components must be done in the event thread.
  • All querying of the state of components must be done in a thread-safe manner.
  • Any variable, either in a user interface or in a handler, that is set from the event thread needs to be read in a thread-safe manner.

The class UI contains a lot of methods for making it easy to follow these principles.

Swing Extreme Testing The Extreme approach to complete Java application testing
Published: June 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Lindsay Peters

Lindsay Peters is the Chief Technical Officer for Pacific Knowledge Systems. He an experience of 25 years in software management, formal analysis, algorithm development, software design, and implementation for large commercial and defense systems. Ten years ago, Lindsay and his team were the early adopters of Java, coupled with more rigorous design processes such as Design by Contract. He then helped transition the development team to the Extreme Programming model. Out of this exciting and successful experience grew the "Extreme Testing" approach. In the early 80's, Lindsay managed a software team that was one of the first to incorporate the newly discovered simulated annealing algorithm into a commercial application. This team solved a previously intractable real-world problem, which was the optimum assignment of radio frequencies to collocated mobile radios. Apart from software development and artificial intelligence systems, Lindsay has an interest in mathematical convexity, and has helped to progress the "Happy Ending" problem. He is also involved in politics, and in the last Australian Federal election he stood as the Greens candidate for the seat of Bennelong.

Contact Lindsay Peters

Tim Lavers

Tim Lavers is a Senior Software Engineer at Pacific Knowledge Systems, which produces LabWizard—the gold standard for rules-based knowledge acquisition software. In developing and maintaining LabWizard for almost 10 years, Tim has worked with many Java technologies, including network programming, Swing, reflection, logging, JavaHelp, web services, RMI, WebStart, preferences, internationalization, concurrent programming, XML, and databases. He has worked with tools as well, such as Ant and CruiseControl. His job also includes a healthy mix of user training, technical support, and support to marketing. In his previous job, he wrote servlets and built an image processing library. Along with his professional programming, he writes and maintains the distributed testing tool, GrandTestAuto. He has published a JavaWorld article on RMI as well as a number of mathematical papers. Tim's hobbies include running and playing the piano.

Contact Tim Lavers

Books From Packt

DWR Java AJAX Applications
DWR Java AJAX Applications

Java EE 5 Development with NetBeans 6
Java EE 5 Development with NetBeans 6

Service Oriented Architecture with Java
Service Oriented Architecture with Java

Apache JMeter
Apache JMeter

Java EE 5 Development using GlassFish Application Server
Java EE 5 Development using GlassFish Application Server

EJB 3 Developer Guide
EJB 3 Developer Guide

Quickstart Apache Axis2
Quickstart Apache Axis2

Liferay Portal Enterprise Intranets
Liferay Portal Enterprise Intranets

 

 

 

 

 

 

No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
X
2
y
U
X
W
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software