Wednesday, 12 June 2013

First example: ValidatingRequestBodyMethodArgumentResolver

 One problem found when implementing a REST server, is that of input validation. Especially for objects, it is common to want to use the JSR-303 @Valid annotation to ensure your input is valid. Spring currently has an open bug, SPR-6709, which states that the @Valid annotation on an @RequestBody parameter does not work. Recent activity indicates it will not be with us much longer, but it is a good example to extend existing functionality.
Below is a class we have implemented to solve this problem. It delegates the translation of the request body to the original class, RequestResponseBodyMethodProcessor, and then validates the result if needed:


In this class, the delegation is clearly visible. The injected dependency to RequestMappingHandlerAdapter seems unfortunate, but it handles the underlying HttpMessageConverter implementations much better than we could do. Finally, the exception class ValidationException translates the JSR-303 constraint violations into a form we want.
Lastly, we need to configure Spring MVC to use our class. We use an XML configuration, which looks like this:



The end result is that @Valid and @RequestBody play nice together. 

Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface

 A familiar problem when implementing a REST server, is that your framework limits what you can do. Sometimes, this problem can be mitigated (not solved) by reformulating (i.e. changing) your business needs. For example, you can reformulate save multiple changes to an invoice and its invoice lines with one click on a save button to save each of multiple changes to an invoice and its invoice lines as separate UI actions. Unfortunately, this change increases your audit trail and thus technology trumps business needs.

Another limit is that conversions have no context. This makes the conversions easier, but also makes functionality like partial updates impossible. That is, it is not possible to that a request body can contain any subset of the changeable properties of a resource, and that all properties that are left out are left at their current values. The default Spring implementation, for example, treats undefined properties as set to their default value. Our goal was to change that to keep the current value in the database.

In this in-depth technical article, I will show you how the new Spring 3.1 interface HandlerMethodArgumentResolver can be used to remove such framework limits. The result is that your business needs are again in control, not the technology.

Background

Spring MVC 3.0 supports REST using the annotation @RequestMapping. This annotation is also used to map normal web application requests to a method of an @Controller bean. With @RequestMapping you can specify the URL being mapped and the HTTP method that a controller method understands. You can also use the annotations @PathVariable and @RequestParam to extract URL templates and request parameters from requests, respectively. The pièce de résistance for REST are the annotations @RequestBody and @ResponseBody, which map Java classes from the request body, respectively to the response body.

If you look at the code, you will see that the translation from the request to the invocation of the controller method is done using a big if/else statement in the class HandlerMethodInvoker (used in AnnotationMethodHandlerAdapter). Not the prettiest solution, and nearly impossible to extend. You can augment some functionality by using AOP, but nothing more.

Spring 3.1

Spring 3.1 will improve this by using a new class, RequestMappingHandlerAdapter, which implements a strategy pattern using the interface HandlerMethodArgumentResolver. The old/current implementation, AnnotationMethodHandlerAdapter, will remain for backward compatibility.

The core of the new implementation (at least for developers) is the interface HandlerMethodArgumentResolver. This interface has two methods: supportsParameter(MethodParameter) to check if a method parameter is supported, and resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory) to actually resolve the method parameter from the request. Default implementations exist for all supported parameters of @RequestMapping annotated methods.

The class RequestMappingHandlerAdapter checks all method parameters against its lists of custom and default HandlerMethodArgumentResolver instances, and the first one that says it supports the method parameter will be used to resolve it.

This new interface gives us some very nice opportunities. I will explain some of them by demonstrating how easy it is to implement your own HandlerMethodArgumentResolver. The examples will show how to extend the REST framework provided by Spring, including adjusting the behavior of the existing Spring implementations.

The Spring version we are using is the latest and greatest, so first we need to add the Spring snapshot repository to our pom.xml:

And finally, we will need to adjust the Spring version of our dependencies to: 3.1.0.BUILD-SNAPSHOT


Monday, 10 June 2013

Custom JComboBox Popup in Swing

Call It in your class....

jComboBox=new JComboBox(vector);

BoundsPopupMenuListener listener =
   new BoundsPopupMenuListener(true, true);

jComboBox.addPopupMenuListener( listener );

jComboBox.setPrototypeDisplayValue("ItemWWW");


-------------------------------------------------------------------------------------------------------------------------------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;

/**
 *  This class will change the bounds of the JComboBox popup menu to support
 *  different functionality. It will support the following features:
 *  -  a horizontal scrollbar can be displayed when necessary
 *  -  the popup can be wider than the combo box
 *  -  the popup can be displayed above the combo box
 *
 *  Class will only work for a JComboBox that uses a BasicComboPop.
 */
public class BoundsPopupMenuListener implements PopupMenuListener
{
private boolean scrollBarRequired = true;
private boolean popupWider;
private int maximumWidth = -1;
private boolean popupAbove;
private JScrollPane scrollPane;

/**
*  Convenience constructore to allow the display of a horizontal scrollbar
*  when required.
*/
public BoundsPopupMenuListener()
{
this(true, false, -1, false);
}

/**
*  Convenience constructor that allows you to display the popup
*  wider and/or above the combo box.
*
*  @param popupWider when true, popup width is based on the popup
*                    preferred width
*  @param popupAbove when true, popup is displayed above the combobox
*/
public BoundsPopupMenuListener(boolean popupWider, boolean popupAbove)
{
this(false, popupWider, -1, popupAbove);
}

/**
*  Convenience constructor that allows you to display the popup
*  wider than the combo box and to specify the maximum width
*
*  @param maximumWidth the maximum width of the popup. The
*                      popupAbove value is set to "true".
*/
public BoundsPopupMenuListener(int maximumWidth)
{
this(true, true, maximumWidth, false);
}

/**
*  General purpose constructor to set all popup properties at once.
*
*  @param scrollBarRequired display a horizontal scrollbar when the
*         preferred width of popup is greater than width of scrollPane.
*  @param popupWider display the popup at its preferred with
*  @param maximumWidth limit the popup width to the value specified
*         (minimum size will be the width of the combo box)
*  @param popupAbove display the popup above the combo box
*
*/
public BoundsPopupMenuListener(
boolean  scrollBarRequired, boolean popupWider, int maximumWidth, boolean popupAbove)
{
setScrollBarRequired( scrollBarRequired );
setPopupWider( popupWider );
setMaximumWidth( maximumWidth );
setPopupAbove( popupAbove );
}

/**
*  Return the maximum width of the popup.
*
*  @return the maximumWidth value
*/
public int getMaximumWidth()
{
return maximumWidth;
}

/**
*  Set the maximum width for the popup. This value is only used when
*  setPopupWider( true ) has been specified. A value of -1 indicates
*  that there is no maximum.
*
*  @param maximumWidth  the maximum width of the popup
*/
public void setMaximumWidth(int maximumWidth)
{
this.maximumWidth = maximumWidth;
}

/**
*  Determine if the popup should be displayed above the combo box.
*
*  @return the popupAbove value
*/
public boolean isPopupAbove()
{
return popupAbove;
}

/**
*  Change the location of the popup relative to the combo box.
*
*  @param popupAbove  true display popup above the combo box,
*                     false display popup below the combo box.
*/
public void setPopupAbove(boolean popupAbove)
{
this.popupAbove = popupAbove;
}

/**
*  Determine if the popup might be displayed wider than the combo box
*
*  @return the popupWider value
*/
public boolean isPopupWider()
{
return popupWider;
}

/**
*  Change the width of the popup to be the greater of the width of the
*  combo box or the preferred width of the popup. Normally the popup width
*  is always the same size as the combo box width.
*
*  @param popupWider  true adjust the width as required.
*/
public void setPopupWider(boolean popupWider)
{
this.popupWider = popupWider;
}

/**
*  Determine if the horizontal scroll bar might be required for the popup
*
*  @return the scrollBarRequired value
*/
public boolean isScrollBarRequired()
{
return scrollBarRequired;
}

/**
*  For some reason the default implementation of the popup removes the
*  horizontal scrollBar from the popup scroll pane which can result in
*  the truncation of the rendered items in the popop. Adding a scrollBar
*  back to the scrollPane will allow horizontal scrolling if necessary.
*
*  @param scrollBarRequired  true add horizontal scrollBar to scrollPane
*                            false remove the horizontal scrollBar
*/
public void setScrollBarRequired(boolean scrollBarRequired)
{
this.scrollBarRequired = scrollBarRequired;
}

/**
*  Alter the bounds of the popup just before it is made visible.
*/
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e)
{
JComboBox comboBox = (JComboBox)e.getSource();

if (comboBox.getItemCount() == 0) return;

final Object child = comboBox.getAccessibleContext().getAccessibleChild(0);

if (child instanceof BasicComboPopup)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
customizePopup((BasicComboPopup)child);
}
});
}
}

protected void customizePopup(BasicComboPopup popup)
{
scrollPane = getScrollPane(popup);

if (popupWider)
popupWider( popup );

checkHorizontalScrollBar( popup );

//  For some reason in JDK7 the popup will not display at its preferred
//  width unless its location has been changed from its default
//  (ie. for normal "pop down" shift the popup and reset)

Component comboBox = popup.getInvoker();
Point location = comboBox.getLocationOnScreen();

if (popupAbove)
{
int height = popup.getPreferredSize().height;
popup.setLocation(location.x, location.y - height);
}
else
{
int height = comboBox.getPreferredSize().height;
popup.setLocation(location.x, location.y + height - 1);
popup.setLocation(location.x, location.y + height);
}
}

/*
*  Adjust the width of the scrollpane used by the popup
*/
protected void popupWider(BasicComboPopup popup)
{
JList list = popup.getList();

//  Determine the maximimum width to use:
//  a) determine the popup preferred width
//  b) limit width to the maximum if specified
//  c) ensure width is not less than the scroll pane width

int popupWidth = list.getPreferredSize().width
  + 5  // make sure horizontal scrollbar doesn't appear
  + getScrollBarWidth(popup, scrollPane);

if (maximumWidth != -1)
{
popupWidth = Math.min(popupWidth, maximumWidth);
}

Dimension scrollPaneSize = scrollPane.getPreferredSize();
popupWidth = Math.max(popupWidth, scrollPaneSize.width);

//  Adjust the width

scrollPaneSize.width = popupWidth;
scrollPane.setPreferredSize(scrollPaneSize);
scrollPane.setMaximumSize(scrollPaneSize);
}

/*
*  This method is called every time:
*  - to make sure the viewport is returned to its default position
*  - to remove the horizontal scrollbar when it is not wanted
*/
private void checkHorizontalScrollBar(BasicComboPopup popup)
{
//  Reset the viewport to the left

JViewport viewport = scrollPane.getViewport();
Point p = viewport.getViewPosition();
p.x = 0;
viewport.setViewPosition( p );

//  Remove the scrollbar so it is never painted

if (! scrollBarRequired)
{
scrollPane.setHorizontalScrollBar( null );
return;
}

// Make sure a horizontal scrollbar exists in the scrollpane

JScrollBar horizontal = scrollPane.getHorizontalScrollBar();

if (horizontal == null)
{
horizontal = new JScrollBar(JScrollBar.HORIZONTAL);
scrollPane.setHorizontalScrollBar( horizontal );
scrollPane.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
}

// Potentially increase height of scroll pane to display the scrollbar

if (horizontalScrollBarWillBeVisible(popup, scrollPane))
{
Dimension scrollPaneSize = scrollPane.getPreferredSize();
scrollPaneSize.height += horizontal.getPreferredSize().height;
scrollPane.setPreferredSize(scrollPaneSize);
scrollPane.setMaximumSize(scrollPaneSize);
scrollPane.revalidate();
}
}

/*
*  Get the scroll pane used by the popup so its bounds can be adjusted
*/
protected JScrollPane getScrollPane(BasicComboPopup popup)
{
JList list = popup.getList();
Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list);

return (JScrollPane)c;
}

/*
*  I can't find any property on the scrollBar to determine if it will be
*  displayed or not so use brute force to determine this.
*/
protected int getScrollBarWidth(BasicComboPopup popup, JScrollPane scrollPane)
{
int scrollBarWidth = 0;
JComboBox comboBox = (JComboBox)popup.getInvoker();

if (comboBox.getItemCount() > comboBox.getMaximumRowCount())
{
JScrollBar vertical = scrollPane.getVerticalScrollBar();
scrollBarWidth = vertical.getPreferredSize().width;
}

return scrollBarWidth;
}

/*
*  I can't find any property on the scrollBar to determine if it will be
*  displayed or not so use brute force to determine this.
*/
protected boolean horizontalScrollBarWillBeVisible(BasicComboPopup popup, JScrollPane scrollPane)
{
JList list = popup.getList();
int scrollBarWidth = getScrollBarWidth(popup, scrollPane);
int popupWidth = list.getPreferredSize().width + scrollBarWidth;

return popupWidth > scrollPane.getPreferredSize().width;
}

@Override
public void popupMenuCanceled(PopupMenuEvent e) {}

@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
{
//  In its normal state the scrollpane does not have a scrollbar

if (scrollPane != null)
{
scrollPane.setHorizontalScrollBar( null );
}
}
public static void main(String[] args) {
new BoundsPopupMenuListener();
}
}

Saturday, 8 June 2013

Capture the Screen with Java’s Robot Class

While surfing through internet, I came to this amazing piece of code in Java that takes the screen shot of your desktop and save it in a PNG file.
This example uses java.awt.Robot class to capture the screen pixels and returns a BufferedImage. Java.awt.Robot class is used to take the control of mouse and keyboard. Once you get the control, you can do any type of operation related to mouse and keyboard through your java code. This class is used generally for test automation.
Copy and paste following code in your Java class and invoke the method captureScreen() with file name as argument. The screen shot will be stored in the file that you specified in argument.

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class SimpleScreenCapture {
public static void main(String[] args) throws Exception {
Rectangle rectangle = new Rectangle(Toolkit.getDefaultToolkit()
.getScreenSize());

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Robot robot = new Robot();
BufferedImage img = robot.createScreenCapture(rectangle);
frame.add(new JLabel(new ImageIcon(img)));
frame.pack();
frame.setVisible(true);
File file = new File("screenshot.gif");

ImageIO.write(img, "gif", file);
}
}