Nelz's Blog

19 January 2010

More Maven Angst

Filed under: Java — nelz9999 @ 15:20

I don’t want to just jump on Maven-bashing bandwagon here, but a casual perusal of my previous posts will show that I talk about Maven a lot. This is going to be another one of those posts.

I’ve been starting to play with Google App Engine, both for personal projects and at work. Since most of my environments are Maven-based, I thought I’d go out and find a Maven Archetype or some kind of a POM file to bring these projects into my environment. I did find several blog posts trying to help: here, here, etc… The annoying thing you’ll notice on those blog posts is that Brian Fox ((formerly?) of Sonatype) posts a comment pointing towards Jason van Zyl’s post describing his efforts to Maven-ize the GAE SDK.

That would be all well and good if things had actually progressed. If you look at JvZ’s post, you’ll see he ends with “hopefully I’ll be able to get this out for Maven users by the end of the week!” What happens after that? Bupkis, nil, nothing, NADA!

There’s comments as late as Dec ‘09 expressing interest to use and/or help with the effort. But, there is NO RESPONSE from anyone at Sonatype.

I just find it galling that Sonatype would go on an all-out publicity spree talking people out of working on their own because JvZ himself was going to bring his skills to bear on the problem, only to completely fizzle out. I suspect the community at large would have been better off if those 4 or 5 early Maven + GAE adopters had continued on their own path, even if JvZ had succeeded.

Again: my patience with Maven/JvZ is waning. I’m eager to see what comes along next to take its place.

20 October 2009

Precompile JSPs for Tomcat 6

Filed under: Java — nelz9999 @ 16:34

At the day job, we are working to upgrade our Tomcat from 5.5.X to 6.0.Y. The biggest problem I found when running our app against Tomcat 6 was that a bunch of JSPs used some quote escaping patterns that the later version of the compile considered to be syntax errors.

To get a feel for how many of these problems existed (after I just-in-time fixed the individual JSPs that crossed my path), the boss wanted me to run a precompile against our whole JSP codebase.

I found the instructions on the Tomcat site, which proved to be incomplete and/or incorrect (of course).

Here is what I ended up with:

<project name="Webapp Precompilation" default="all" basedir=".">
   <import file="${tomcat.home}/bin/catalina-tasks.xml"/>
   <target name="jspc">
       <jasper
             validateXml="false"
             uriroot="${webapp.path}"
             webXmlFragment="${webapp.path}/WEB-INF/generated_web.xml"
             outputDir="${webapp.path}/WEB-INF/src"
             compilerTargetVM="1.5"
             compilerSourceVM="1.5"
             failOnError="false" />
  </target>

  <target name="compile">
    <mkdir dir="${webapp.path}/WEB-INF/classes"/>
    <mkdir dir="${webapp.path}/WEB-INF/lib"/>
    <javac destdir="${webapp.path}/WEB-INF/classes"
           optimize="off"
           debug="on" failonerror="false"
           srcdir="${webapp.path}/WEB-INF/src"
           source="1.5"
           target="1.5"
           excludes="**/*.smap">
      <classpath>
        <pathelement location="${webapp.path}/WEB-INF/classes"/>
        <fileset dir="${webapp.path}/WEB-INF/lib">
          <include name="*.jar"/>
        </fileset>
        <pathelement location="${tomcat.home}/lib"/>
        <fileset dir="${tomcat.home}/lib">
          <include name="*.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/bin">
          <include name="*.jar"/>
        </fileset>
      </classpath>
      <include name="**" />
      <exclude name="tags/**" />
    </javac>
  </target>

  <target name="all" depends="jspc,compile"></target>

  <target name="cleanup">
        <delete>
        <fileset dir="${webapp.path}/WEB-INF/src"/>
        <fileset dir="${webapp.path}/WEB-INF/classes/org/apache/jsp"/>
        </delete>
  </target>
</project>

The real stroke of luck is this piece:

             compilerTargetVM="1.5"
             compilerSourceVM="1.5"

Their document said stuff about adding some parameters (source="1.5" target="1.5") to the “javac” target, but they neglected the “jasper” target. The error messages complained about the above two parameters being set to 1.4, so as an experiment I plugged them into the ANT target with 1.5’s and the compilation ran correctly!

Another word of note, the “showSuccess” directive did not work as their documentation states, but the “failOnError” directive does.

I hope this helps others!

20 August 2009

Maven Tomcat Plugin

Filed under: Java — nelz9999 @ 10:28

Maven. It is both fantastic, and stress-inducing. And more than just Maven itself, there is a whole community of Maven plugins which are just as frustrating.

Take the Maven Tomcat Plugin for example. It sounds so nice to just type “mvn tomcat:run” in your webapp module, and have it automagically run, dynamically loading any JSP changes. But, if you have a non-trivial webapp, you probably need to send in some system properties.

Let’s pretend that I require the following command-line parameters passed to Tomcat on the command line:

-Dexample.value.1=alpha -Dexample.value.2=beta

How would I send that in to the Tomcat plugin? Well, the documentation says there is a “systemProperties” element where you can sent them in. But what is the format?

Is the format perhaps like the Surefire Plugin?

<systemProperties>
  <property>
    <name>example.value.1</name>
    <value>alpha</value>
  </property>
  <property>
    <name>example.value.2</name>
    <value>beta</value>
  </property>
</systemProperties>

Or perhaps the format is like that of the Jetty Plugin?

<systemProperties>
  <systemProperty>
    <name>example.value.1</name>
    <value>alpha</value>
  </systemProperty>
  <systemProperty>
    <name>example.value.2</name>
    <value>beta</value>
  </systemProperty>
</systemProperties>

Nope. The format is like neither of those. It’s like this:

<systemProperties>
  <example.value.1>alpha</example.value.1>
  <example.value.2>beta</example.value.2>
</systemProperties>

I’ll admit that this format is a bit less verbose. But why, in the name of all that is holy, does it have to have it’s own 1-off syntax?!? And can you find this documented anywhere else? I was sure challenged finding it.

This is the kind of non-documentation and irregularity that gives Maven a bad name and keeps my blood pressure high.

UPDATE (1 Nov 09): As she says in the comments, Kathy actually took the time to submit a ticket to the Maven Tomcat Plugin maintainers, rather than going with my tactic of passively bitching. And today I get notified by herion that the suggestion was taken and there is now public documentation added about these properties.

30 July 2009

Maven, TestNG, and SeleniumRC

Filed under: Java — nelz9999 @ 23:01

Today, I set up a Maven module to run SeleniumRC (Java) tests via TestNG. It wasn’t a straightforward or easy task, so I thought I’d share some of what I found with you.

We opt to have all of our modules stay in lock-step version-wise, so we keep a pretty large structure in our SVN trunk. We include/exclude modules for compilation by using Maven profiles, so we don’t have to build the whole structure if we are only changing one small component. Our top-level pom.xml file looks similar to this:

<project>
  <groupId>...
  <artifactId>...
  <version>...
  <packaging>pom</packaging>
  <modules>
    <module>baseModule</module>
  </modules>
  <profiles>
    <profile>
      <id>all</id>
      <modules>
        <module>otherModule</module>
        ...
        <module>web-test</module>
      </modules>
    </profile>
    <profile>
      <id>web-test</id>
      <modules>
        <module>web-test</module>
      </modules>
    </profile>
  </profiles>
</project>

As you can see, running “mvn clean install” at this top level would only end up building the baseModule. To get all the modules, we run “mvn clean install -Pall” which builds baseModule along with otherModule and web-test and anything else as represented by the ellipse. The web-test module is the one we will be interested in.

For our heavy-weight tests (non-unit tests, like functional or web testing), we like to have the module compile, but not run unless we specifically trigger the tests. To achieve this, we create a pom.xml (for the web-test module) that looks something like this:

<project>
  <parent>...
  <artifactId>web-test</artifactId>
  <packaging>pom</packaging>
  <build>
    <testSourceDirectory>src/it/java</testSourceDirectory>
    <testResources>
      <testResource>
        <directory>src/it/resources</directory>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.0.2</version>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.4.2</version>
        <configuration>
          <testSourceDirectory>src/it/java</testSourceDirectory>
          <excludedGroups>web-test</excludedGroups>
        </configuration>
        <executions>
          <execution>
            <phase>integration-test</phase>
            <goals>
              <goal>test</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

This pom has several features:

  • It sets up src/it/java as the location holding the source code for the tests
  • It sets the surefire plugin (which runs your tests) to only run at the integration-test part of the Maven lifecycle
  • It says to ignore any tests in the TestNG “web-test” group
  • Note the packaging says this module is of the type “pom”. I found that if this is set to “jar”, the tests run twice for some unknown reason

Now, if you had tests in src/it/java (that were all annotated as “web-test”) and you ran “mvn clean install”, you’d see that zero tests ran. This is what we want! Now, we have to get the tests running if we pass a specific profile in. To achieve this, I added the following profile definition to the web-test pom.xml.

<profiles>
    <profile>
      <id>run-web-test</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.4.2</version>
            <configuration>
              <suiteXmlFiles>
                <suiteXmlFile>src/it/resources/testng.xml</suiteXmlFile>
              </suiteXmlFiles>
              <!--<groups>web-test</groups>-->
              <!--<forkMode>always</forkMode>-->
            </configuration>
            <executions>
              <execution>
                <phase>integration-test</phase>
                <goals>
                  <goal>test</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

This profile is invoked when you run “mvn clean install -Prun-web-test”. There are several things I’d like to point out here:

  • At runtime, this overrides the previously-declared surefire plugin configuration, but only when invoking the profile.
  • Originally, I was mostly getting stuff to work without the <suiteXmlFiles /> section, but with the <groups /> and <forkMode /> sections… But it was running every single test class as it’s own TestSuite.
  • To fix the multiple TestSuite (and @Before*, @After* problems that I’ll discuss later), I ended creating my own testng.xml file. Having this file and using the <suiteXmlFiles /> element made things a whole lot easier for configuring my TestNG executions than trying to configure the tests through the clumsy interference of the surefire plugin.
  • As much as I like Maven, I have to say that the lack of documentation and general obfuscation that happens when using their plugins can really drive a person BATTY!

Now, I need to start integrating the stuff I need for using SeleniumRC. I found some decent help on how to start up and shut down the selenium-server, however I disagree with using a post-integration-test phase to shut down the server, because if the tests fail in the integration-test, Maven won’t progress to the next phase and you’ll be left with a spuriously running selenium-server. The client API’s do have access to the command that the was being run to shut the server down, so it is just a matter of having some TestNG @AfterSuite or @AfterGroup method issue the Selenium.shutDownSeleniumServer() command.

This brings me to some of the TestNG wonkiness I saw. I decided to use a Singleton Data Provider (named  SeleniumFactory) to deliver the Selenium objects to the individual tests. (Selenium objects are time-expensive to create, so I wanted to only create 1 for the duration of the entire test suite.) I wanted to initialize the SeleniumFactory object in a @BeforeGroup method, deliver the Selenium object as a Data Provider, and then tear the SeleniumFactory down in a @AfterGroup method. (At first, I was fighting Maven’s handling of each test as its own test suite which was giving me sequence issues, which is why I ended up defining my own testng.xml file, as I stated above.) Let me start with some code to show you what I mean:

public class SequenceEndsTest {
    @BeforeGroups(groups = {"web-test"})
    public void startupSelenium() {
        System.out.println("-- > Called startup.");
    }
    @AfterGroups(groups = {"web-test"}, alwaysRun = true)
    public void cleanupSelenium() {
        System.out.println("-- > Called cleanup.");
    }
}
public class SeleniumFactory {
    @DataProvider(name = "selenium")
    public static Object[][] getSelenium() {
        System.out.println("-- > Called data provider.");
        return new Object[][] { new Object[] {null}};
    }
}
public class SequenceTest {
    @Test(groups={"web-test"},
            dataProvider = "selenium", dataProviderClass = SeleniumFactory.class)
    public void testSomething(final Selenium selenium) {
        System.out.println("-- > Called test.");
    }
}

This is what I expected to see after running this code:

-- > Called startup.
-- > Called data provider.
-- > Called test.
-- > Called cleanup.

But this is what I *actually* saw:

-- > Called data provider.
-- > Called startup.
-- > Called test.
-- > Called cleanup.

I would have assumed that an @BeforeGroup method got called before a data provider method, but I guess my assumption is wrong, eh? After I figured out this order-of-method-calls business, I was able to adapt my SeleniumFactory accordingly.

… And that is my story. To summarize: each of these tools could be better documented, but at the end of the day I was able to get a Maven module up and running SeleniumRC tests via TestNG. Now, on to the test-writing!

28 May 2009

The Life of a boolean

Filed under: Java — nelz9999 @ 23:59

Java booleans came up in conversation today, and I wanted to point out a couple of things that I’ve noticed.

Firstly, when talking about (capital ‘B’) Booleans, it is possible to achieve a trinary state object: TRUE/FALSE/null. However, it is not recommended. In fact, I’d go so far as to recommend breaking the fingers of anyone who tried to perpetrate this horrible pattern in your code.

Secondly, I’d like to discuss booleans in your model objects. I agree that it frequently seems like a good idea to include one in your model object. However, I have seen so many occurrences where, over time, it becomes necessary to add a third (or fourth, or fifth) state. Even the RDBMSes are hedging their bets by storing boolean flags as TINYINTs. I would suggest that you seriously consider using an Enum to represent your two states as they exist at the beginning of their lifecycle… This way you won’t get stuck with a bunch of isXXX() accessors that all have to be refactored as soon as you add a third state.

13 May 2009

JDK 1.6 on OS X

Filed under: Java — nelz9999 @ 15:32

For Widgetbox I was recently tasked with investigating a new Java memcached client called xmemcached.

Things were going smoothly (except that the xmemcached documentation is written mostly in Chinese)… But then I hit a bump in the road: the xmemcached JAR file was compiled for JDK 1.6.

Up until this point, all our code was using JDK 1.5, and we had only vague plans to eventually upgrade to JDK 1.6. Upon discussion, I’m just gonna work on the 1.6 stuff in a branch until we get the rest of the development team and operating environments up to a JDK 1.6 standard.

So, I went about upping my chosen JDK via the "Java Preferences" dialog.

Now, I saw something like this:

$ java -version
java version "1.6.0_07"
Java(TM) SE Runtime Environment (build 1.6.0_07-b06-153)
Java HotSpot(TM) 64-Bit Server VM (build 1.6.0_07-b06-57, mixed mode)

Awesome. That’s exactly what I wanted!

But hold the phone, I was also getting this:

$ mvn -v
Maven version: 2.0.9
Java version: 1.5.0_16
OS name: "mac os x" version: "10.5.6" arch: "i386" Family: "unix"

So, I did some digging. With some hints, I found that the executable /usr/bin/mvn uses the symlink "CurrentJDK" to figure out which JDK to run, and the "Java Preferences" panel doesn’t update that when you change JDKs. (Dumb!)

So, here’s how I fixed it:

$ cd /System/Library/Frameworks/JavaVM.framework/Versions/
$ ls -la
...
lrwxr-xr-x   1 root  wheel    5 Sep 29  2008 1.5 -> 1.5.0
drwxr-xr-x   8 root  wheel  272 Feb 21  2008 1.5.0
lrwxr-xr-x   1 root  wheel    5 Sep 29  2008 1.6 -> 1.6.0
drwxr-xr-x   8 root  wheel  272 May  6  2008 1.6.0
lrwxr-xr-x   1 root  wheel    3 May 12 18:18 CurrentJDK -> 1.5
...
$ sudo rm CurrentJDK
$ sudo ln -s 1.6 CurrentJDK

Now, I am seeing the environment I want:

$ ls -la
...
lrwxr-xr-x   1 root  wheel    5 Sep 29  2008 1.5 -> 1.5.0
drwxr-xr-x   8 root  wheel  272 Feb 21  2008 1.5.0
lrwxr-xr-x   1 root  wheel    5 Sep 29  2008 1.6 -> 1.6.0
drwxr-xr-x   8 root  wheel  272 May  6  2008 1.6.0
lrwxr-xr-x   1 root  wheel    3 May 12 18:18 CurrentJDK -> 1.6
...
$ mvn -v
Maven version: 2.0.9
Java version: 1.6.0_07
OS name: "mac os x" version: "10.5.6" arch: "x86_64" Family: "mac"

25 March 2009

Java 5 Auto-Unbox Is Not NULL Safe

Filed under: Java — nelz9999 @ 16:03

Yuck!

I am kinda getting fed up with the way the language implementers of Java continually add all these conveniences, but leave them 1/2 baked.

Example:

01public class UnboxTest {
02    @Test
03    public void testUnbox() {
04        final Integer a = Integer.valueOf(10);
05        final Integer b = null;
06        unboxIt(a);
07        unboxIt(b);
08    }
09    void unboxIt(final int value) {
10        System.out.println("Value: " + value);
11    }
12}

Guess what happens when you run this code?

A big, fat NullPointerException on line 7.

Oh yeah… Autoboxing is supposed to be soo helpful, but they can’t default a frigging null to the default value of an int. (I.e. 0)

See my previous post for similar problems in the Java 5 foreach syntax.

9 January 2009

SeleniumRC and FireFox3

Filed under: Java — nelz9999 @ 14:21

We have started playing with Selenium for some automated testing at my day job. (I’ve been a proponent of Selenium, but I haven’t played with it directly for a bit.)

I ran into a problem, as described by the Space Vatican. Luckily, he also had the solution to the problem.

I took his command-line instructions and created two scripts to run those command on a given file (‘cuz my file was named differently than the one in his script). Here are the contents of the script:

jar xf $1 customProfileDirCUSTFFCHROME/extensions/readystate@openqa.org/install.rdf
jar xf $1 customProfileDirCUSTFFCHROME/extensions/{538F0036-F358-4f84-A764-89FB437166B4}/install.rdf
jar xf $1 customProfileDirCUSTFFCHROME/extensions/\{503A0CD4-EDC8-489b-853B-19E0BAA8F0A4\}/install.rdf
jar xf $1 customProfileDirCUSTFF/extensions/readystate\@openqa.org/install.rdf
jar xf $1 customProfileDirCUSTFF/extensions/\{538F0036-F358-4f84-A764-89FB437166B4\}/install.rdf
jar uf $1 customProfileDirCUSTFFCHROME/extensions/readystate@openqa.org/install.rdf
jar uf $1 customProfileDirCUSTFFCHROME/extensions/{538F0036-F358-4f84-A764-89FB437166B4}/install.rdf
jar uf $1 customProfileDirCUSTFFCHROME/extensions/\{503A0CD4-EDC8-489b-853B-19E0BAA8F0A4\}/install.rdf
jar uf $1 customProfileDirCUSTFF/extensions/readystate\@openqa.org/install.rdf
jar uf $1 customProfileDirCUSTFF/extensions/\{538F0036-F358-4f84-A764-89FB437166B4\}/install.rdf

And if you’d like, and feel like trusting me, here is the resultant, FF3-friendly, jar file : selenium-server-1.0-beta-1.jar

18 October 2008

Compile-Time Dependencies

Filed under: Java — nelz9999 @ 11:57

When building out your architecture, I would encourage you to break out your APIs as first-class modules. (Sorry, a ‘module’ is Maven speak for the parts of a project that create their own artifacts such as JAR or WAR files.)

For an example of a simple suggested structure, look at the following image. (Arrows denote compile-time dependencies.):

IdealModule

In some of my recent projects I received some push-back on breaking out the APIs. The concern was that breaking them out as modules would add overhead to the build process. I do understand that concern, but I think it is a minimized threat if you are using a fairly sophisticated build system.

I acquiesced at the time, and now we have a structure that looks similar to the next image. (Can you see the problem already?):PreviousModule

The black arrow indicates just one of the weaknesses of this model. Here, you can see that a UI component can (at the least offensive) access the DAO API as well as (at the most offensive) access the concrete DAO implementations.

When you have a structure like this, the question is not if, but when will one of your fellow engineers take advantage of this and circumvent the nice functional striations that you have taken so much time to isolate?

14 October 2008

XMLUnit and TestNG

Filed under: Java — nelz9999 @ 17:50

Our entire testing system is based on TestNG, but one of our developers recently invoked XMLUnit to test some of the XML he was producing.

Now, XMLUnit is built to be used with JUnit (old JUnit… like 3.8.1 style JUnit…), which at first glance doesn’t seem to play well with TestNG test runners. (We were having some build timeouts and other mysterious behavior when trying to run these tests.)

To give you an idea, here is a snippet from the examples:

package net.nelz.test.xmlunit;
import org.custommonkey.xmlunit.XMLTestCase;
public class MyXMLTestCase extends XMLTestCase {
public MyXMLTestCase(String name) {
super(name);
}
public void testForEquality() throws Exception {
String myControlXML = "<msg><uuid>0x00435A8C</uuid></msg>";
String myTestXML = "<msg><localId>2376</localId></msg>";
assertXMLEqual("comparing test xml to control xml", myControlXML, myTestXML);
assertXMLNotEqual("test xml not similar to control xml", myControlXML, myTestXML);
}
}

If you look at that example, the only methods that seem to add any value are assertXMLEqual(...) and assertXMLNotEqual(...).

If you look at the source code for those methods, you’ll realize that they just delegate to parallel static methods in the class XMLAssert:

...
public void assertXMLEqual(String control, String test)
throws SAXException, IOException {
XMLAssert.assertXMLEqual(control, test);
}
...

What this all means is that XMLUnit already is TestNG friendly. Here’s what that snippet from XMLUnit’s example code would look like if it were rewritten to target TestNG.:

package net.nelz.test.xmlunit;
import org.custommonkey.xmlunit.XMLAssert;
import org.testng.annotations.Test;
public class MyXMLTestCase {
@Test
public void testForEquality() throws Exception {
String myControlXML = "<msg><uuid>0x00435A8C</uuid></msg>";
String myTestXML = "<msg><localId>2376</localId></msg>";
XMLAssert.assertXMLEqual("comparing test xml to control xml", myControlXML, myTestXML);
XMLAssert.assertXMLNotEqual("test xml not similar to control xml", myControlXML, myTestXML);
}
}
Older Posts »

Blog at WordPress.com.