Grails vs. Rails vs. Roo!?

There are some great choices out there to choose from when starting a new web development project. Whether you are just doing a simple site or starting your own business. The frameworks, very thankfully, are getting so good. Which one will manage to stand the test of time? The graph below doesn’t mean that the most trend is always going to be the highest one, especially since roo is so fresh and in the past year has been improved dramatically.

ConverterNotFound Using Spring Roo

Using Roo has been really awesome! I would recommend it to anyone starting a web project for the first time that really wanted to use Spring and Java.

Just ran into this error tonight and thought I would blog a little bit about it in case others run into the same error… *plus* I wanted to show a bit of this new Google Guava project!

1
2
3
4
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from 'java.util.Se
t'
to 'java.lang.String'
        at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:181)
        at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:66)

This meant that we need a converter for the show view since the default create from Roo does not take care of the ONE_TO_MANY relationship. Below is the code that was needed in my case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * A central place to register application Converters and Formatters.
 */

@RooConversionService
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    Converter<Set<Property>, String> getPropertySetConverter() {
        return new Converter<Set<Property>, String>() {
            public String convert(Set<Property> properties) {
                return Joiner.on(",").join(properties.toArray());      // 1
            }
        };
    }
  @Override
  protected void installFormatters(FormatterRegistry registry) {
    super.installFormatters(registry);
    registry.addConverter(getPropertySetConverter());
  }
 
}

Notice the line:

1
return Joiner.on(",").join(properties.toArray());      // 1

This is using the Google Guava project to create a String instead of using the old school StringBuilder approach.

Selenium Localization

Not sure how other corporations are localizing Selenium. It has been an issue with ours. Since the creation of selenium we have not embedded localization into the infrastructure…oops, right?! The funny thing is that it took two of us to implement this simple solution in a couple hours. Once done we sent it off for review by the Automation Testing team. I wanted to let others take a look in case they were in the same situation that we were in.

One key is that we call into our Java server using a JSP would change depending upon how you get your localization entries for selenium.

How we localized Selenium:

•Added a class RBUtil that has methods overloaded method getValue(). As an example it is used in the following way:

1
2
3
4
5
String localizedValue = RBUtil.getValue(KEY_TO_LOCALIZATION)

//There are also signatures for the following:
RBUtil.getValue(key, default)
RBUtil.getValue(key, text insert array)

• The way this works is by calling the server with the resource bundle key. We are using the same Tomcat session as selenium to get the localized value.
• A key is that we are re-using the Tomcat session id so that we are not creating new sessions for each resource bundle (or localized string).
• We also cache the locale, username, etc. In fact, we will probably need to do more caching in the future but the main goal is that this will get us on our feet for having selenium localized.

Implementation Class:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.ptc.selenium.localization;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import org.apache.log4j.Logger;

import sun.misc.BASE64Encoder;

import com.ptc.selenium.SeleniumLogger;
import com.ptc.selenium.SeleniumSession;
import com.ptc.selenium.SolventSelenium;
import com.ptc.selenium.SolventTestCase;

/**
 * Singleton class that is used to get the localization values from the RBInfo class file entries.
 *
 * This will retrieve values for selenium tests to be able to localize and attribute clicks to rbinfo files.
 *
 */

public class RBUtil {

    private static final Logger log = SeleniumLogger.getLogger(SolventTestCase.class.getName());

    private static RBUtil singleton;

    private final String baseURL;

    private final String username;

    private final String password;

    private final String cookie;

    private final String locale;

    private RBUtil() {
        SolventSelenium selenium = SeleniumSession.get();

        // Cache these values on the instance
        baseURL = selenium.getBaseURL();
        password = selenium.getPassword();
        username = selenium.getUsername();
        cookie = selenium.getCookieByName("JSESSIONID");
        locale = selenium.getEval("selenium.browserbot.getCurrentWindow().getLocale()");
    }

    public static RBUtil getInstance() {
        if (singleton == null) {
            singleton = new RBUtil();
        }
        return singleton;
    }

    /**
     * Will retrieve the resource bundle value given the key in the form of class.key. For example,
     * com.ptc.windchill.enterprise.folder.folderResource.PARTS_VIEW
     *
     * @param key
     *            In the form of CLASS.KEY. For example,
     *            "com.ptc.windchill.enterprise.folder.folderResource.PARTS_VIEW".
     * @return The value returned from the resource bundle lookup. If the resource could not be found empty string is
     *         returned
     */

    public static String getValue(String key) {
        return getValue(key, "");
    }

    /**
     * Will retrieve the resource bundle value given the key in the form of class.key.
     *
     * @param key
     *            In the form of CLASS.KEY. For example,
     *            "com.ptc.windchill.enterprise.folder.folderResource.PARTS_VIEW".
     * @param def
     *            The default value you want in the case that the locale could not be looked up.
     * @return The value returned from the resource bundle lookup.
     */

    public static String getValue(String key, String def) {
        String value = getInstance().getValueFromServer(key);
        return (value == null) ? def : value;
    }

    /**
     * Gets the localized string, unescapes the quot and puts the inserts in.
     *
     * @param key
     *            In the form of CLASS.KEY. For example,
     *            "com.ptc.windchill.enterprise.folder.folderResource.PARTS_VIEW".
     * @param inserts
     *            A list of inserts in order of the numbering in the rbinfo file.
     * @return The final string.
     */

    public static String getValue(String key, List<String> inserts) {
        String value = getInstance().getValueFromServer(key);

        for (int i = 0; i < inserts.size(); i++) {
            value = value.replace("{" + i + "}", inserts.get(i));
        }

        value = value.replaceAll("&quot;", """);

        return value;
    }

    private String getValueFromServer(String key) {
        InputStream ins = null;
        try {
            log.debug("
Using Session Cookie = " + this.cookie);
            URL url = new URL(this.baseURL + "
/netmarkets/jsp/util/getBundleKey.jsp?key=" + key);
            URLConnection conn = url.openConnection();

            conn.setRequestProperty("
Cookie", "JSESSIONID=" + this.cookie);
            conn.setRequestProperty("
Accept-Language", this.locale);
            conn.setRequestProperty("
Authorization", "Basic " + encode(this.username + ":" + this.password));

            String value = null;
            try {
                ins = conn.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
                value = reader.readLine();
                log.debug("
Fetched RbInfo Key=" + key + " Value=" + value);
            } finally {
                if (ins != null) {
                    ins.close();
                }
            }

            return value;
        } catch (Exception e) {
            throw new RuntimeException("
Unable to reach server.", e);
        }
    }

    /**
     * Encode the string for use in the header.
     *
     * @param source
     *            The string you want to encode using base 64 encoding.
     * @return The encoded string.
     */
    public static String encode(String source) {
        BASE64Encoder enc = new sun.misc.BASE64Encoder();
        return (enc.encode(source.getBytes()));
    }

}