Software engineering is a profession dedicated to designing, implementing, and modifying software so that it is of higher quality, more affordable, maintainable, and faster to build. Wikipedia

February 18, 2010

Uncategorized

1 comment

Last December Ben Alex and his team released Spring Roo 1.0.0.GA. I’ve decided to give it a try. Even though there was some initial criticism I found Roo to be a quite useful tool.

After creating some CRUD controllers I wanted to enable optional relationships in the view (i.e. comboboxes with a null/empty option). As it’s quite a commonly requested feature and it cannot (at least as to my best knowledge) be enabled with toggling a single switch I’ve decided to share here my recipe how to do this.

Let’s say we have Node entity that can reference another node (it’s parent).

Node.java:

@Entity
@RooJavaBean
@RooToString
@RooEntity(identifierType = Integer.class)
public class Node {
 
	@ManyToOne
	@JoinColumn(name = "parent_id")
	private Node parent;
 
	// ...
}

Now let’s add the No parent option to the fragment holding the node parents list in the edit form JSP template:

<form:select cssStyle="width:250px" id="_parent_id" path="parent">
    <form:option value="" label="No parent" />
    <form:options itemValue="id" items="${nodes}" itemLabel="fullPath" />
</form>

After the form is submitted it reaches the NodeController.update() method:

@RequestMapping(method = RequestMethod.PUT)
    public String NodeController.update(@Valid Node node, BindingResult result, ModelMap modelMap) {
    // ...
    }

But before this happens the values that were submitted have to be converted into proper Node properties. The id into an Integer, date time values into a java.util.Date objects, etc. Without going into further details this happens in Spring’s conversion system.
All entity beans by default hit the org.springframework.core.convert.support.IdToEntityConverter converter. The converter basically tries to find a findEntityName() static method on the class that it has to convert to and call it passing the id from the HTML form. It does that regardless whether the id is null or not. And since the default Roo finders throw an IllegalArgumentException, as shown bellow, we quickly see the “Internal Error” page and a long stack trace in the server log.

    public static Node Node.findNode(Integer id) {
        if (id == null) throw new IllegalArgumentException("An identifier is required to retrieve an instance of Node");
        return entityManager().find(Node.class, id);
    }

Here are the two first options for solving this that came to my mind:

  • We can push in all the Roo finders from aspect files into the respective Java classes and change their behaviour. But it’s not the most elegant solution and, yes, it changes the finder behaviour, which might not be desirable.
  • Write specific converters for every entity class in our project.

Neither of these seemed to me a good solution. So I looked into tweaking the Spring the default spring converter. The new IdToEntityOrNullConverter has basically only 2 lines added in the convert() method:

	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		Method finder = getFinder(targetType.getType());
		Object id = this.conversionService.convert(source, sourceType, TypeDescriptor.valueOf(finder
				.getParameterTypes()[0]));
		if (id == null)
			return null;
 
		return ReflectionUtils.invokeMethod(finder, source, id);
	}

And it works!

You need to remember to register the beans in the applicationContext.xml as well:

	<mvc:annotation-driven conversion-service="conversionService" />
	<!--
		There are two conversion services. The generic one is used by idToEntityOrNullConverter as it requires one. We cannot
		use the conversionService that is being used in the MVC context, as we would run into the chicken-and-egg problem
		trying to instantiate the converter and the conversionService.
	-->
	<bean id="genericConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" />
 
	<bean id="idToEntityOrNullConveter" class="org.inszy.spring.support.IdToEntityOrNullConveter">
		<constructor-arg index="0" ref="genericConversionService" />
	</bean>
 
	<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<property name="converters">
			<list>
				<ref bean="idToEntityOrNullConveter" />
			</list>
		</property>
	</bean>

There is also a JIRA issue available for this problem #ROO-581.

IdToEntityOrNullConverter.java listing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.inszy.spring.support;
 
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Set;
 
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
 
public final class IdToEntityOrNullConveter implements ConditionalGenericConverter {
 
	private final GenericConversionService conversionService;
 
	public IdToEntityOrNullConveter(GenericConversionService conversionService) {
		this.conversionService = conversionService;
	}
 
	public Set getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
	}
 
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		Method finder = getFinder(targetType.getType());
		return (finder != null &amp;&amp; this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder
				.getParameterTypes()[0])));
	}
 
	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		Method finder = getFinder(targetType.getType());
		Object id = this.conversionService.convert(source, sourceType, TypeDescriptor.valueOf(finder
				.getParameterTypes()[0]));
		if (id == null)
			return null;
 
		return ReflectionUtils.invokeMethod(finder, source, id);
	}
 
	private Method getFinder(Class&lt; ?&gt; entityClass) {
		String finderMethod = "find" + getEntityName(entityClass);
		Method[] methods = entityClass.getDeclaredMethods();
		for (Method method : methods) {
			if (Modifier.isStatic(method.getModifiers()) &amp;&amp; method.getParameterTypes().length == 1
					&amp;&amp; method.getReturnType().equals(entityClass)) {
				if (method.getName().equals(finderMethod)) {
					return method;
				}
			}
		}
		return null;
	}
 
	private String getEntityName(Class&lt; ?&gt; entityClass) {
		String shortName = ClassUtils.getShortName(entityClass);
		int lastDot = shortName.lastIndexOf('.');
		if (lastDot != -1) {
			return shortName.substring(lastDot + 1);
		} else {
			return shortName;
		}
	}
 
}

Any feedback is greatly appreciated.

UPDATE 2010.02.12: This should no longer be required if you’re using Roo 1.0.2 (or newer) as Andrew Swan noted on the Spring forums.

August 24, 2009

Uncategorized

6 comments

Over a year ago Anand Sreenivasan published a short Java class which purpose was to check for Internet connectivity on the computer of the person running it. It was meant to be used as a CLI app. I decided to make it a little bit more reusable.

Continue reading →

February 6, 2009

Uncategorized

(No comments)

Some time ago Rails 2.2 was released. Since than the framework requires rubygems 1.3.1 to run. Unfortunately Ubuntu 8.10 only has rubygems 1.2.0 in its repositories. A quick google search didn’t yield a good solution for this. Everyone is suggesting to install the vanilla rubygems-1.3.1.tgz, but I feel more comfortable having rubygems managed by Ubuntu’s package manager.  So I created the package myself.

It’s now available from my PPA. To install it you need to add the following lines to your /etc/apt/sources.list file:

deb http://ppa.launchpad.net/maciejb/ppa/ubuntu intrepid main
deb-src http://ppa.launchpad.net/maciejb/ppa/ubuntu intrepid main

For further instructions please follow this link. My PPA’s address is: https://launchpad.net/~maciejb/+archive/ppa.

Update (2009/09/08): Brightbox currently has the most up-to-date Rubygems repository available: http://wiki.brightbox.co.uk/docs:brightboxaptrepository