Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

Entries in chariot-news (45)

Thursday
May102012

Roo add-on development - how to unit test configuration changes

I'm working on updates to several Roo add-ons, which I am going to be pushing out to the Roo repository soon. Here are some challenges and how I overcame them.

The add-on command marker interface

This add-on sets up Coffeescript using a Maven plugin. Here is the add-on source:

package org.sillyweasel.addons.coffeescript;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;

@Component
@Service
public class CoffeescriptCommands implements CommandMarker { 

  /**
   * Get a reference to the CoffeescriptOperations from the underlying OSGi
   * container
   */
  @Reference
  CoffeescriptOperations operations;

  @CliAvailabilityIndicator({"coffeescript setup"})
  public boolean isSetupCommandAvailable() {
    return operations.isSetupCommandAvailable();
  }

  @CliAvailabilityIndicator({"coffeescript remove", "coffeescript addjoinset", 
    "coffeescript removejoinset", 
    "coffeescript listjoinsets"})
  public boolean isCoffeescriptInstalled() {
    return operations.isPluginInstalled();
  }

  @CliCommand(value = "coffeescript setup", 
     help = "Install the CoffeeScript compiler")
  public void setup(
    @CliOption(key = "outputDirectory", mandatory = false,
        unspecifiedDefaultValue = "${project.build.directory}") String outputDirectory,
    @CliOption(key = "coffeeDir", mandatory = false,
        unspecifiedDefaultValue = "src/main/webapp/scripts",
        specifiedDefaultValue = "src/main/webapp/scripts") String coffeeDir,
    @CliOption(key = "bare", mandatory = false,
        unspecifiedDefaultValue = "false",
        specifiedDefaultValue = "true") boolean bare) {

    operations.setup(coffeeDir, outputDirectory, bare);
  }

  public void addJoinSet(
      @CliOption(key = "joinSetId", mandatory = true, specifiedDefaultValue = "main")
      String joinSetId,
      @CliOption(key = "includes", mandatory = true, 
         help = "comma-separated list of search paths for javascript files to include")
      String includes,
      @CliOption(key = "excludes", mandatory = true, 
         help = "comma-separated list of search paths for javascript files to exclude")
      String excludes) {

    // TODO - set up the command

  }

  @CliCommand(value = "coffeescript remove", help = "Remove the coffeescript compiler")
  public void remove() {
    operations.remove();
  }
}

Some of this I haven't written yet (hence the TODOs) but the rest is fleshed out and I'll be going over it later.

Task #1 - Roo addons don't include JUnit?

Right now, no. (ROO-3161) However, you can just add it yerself. I'm using Mockito for mocking (more about that later) as well as messing around with those lovely Hamcrest matchers:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.10</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-all</artifactId>
  <version>1.1</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.9.0</version>
  <scope>test</scope>
</dependency>

Task #2 - You need to widen the visibility of the Roo-injected OSGi objects to mock them

Because we want to unit test our add-ons without firing up an OSGi container, we will need to mock things. I'm using Mockito, an excellent mock and spy library. But I can't get to the injected objects, and the test I'm going to show here has to make a "mockery" of the ProjectOperations Roo service. It is defined this way in the code of the Operations implementation class:

private @Reference ProjectOperations projectOperations;

The problem with this is that I can't just use Mockito's mocking method to access and fake it out. So, I widened to friendly scope, which allows access from the same package. I guess the better way would maybe have been to create a getter but then I'm exposing it in a wider range than just the add-on's specific package:

@Reference ProjectOperations projectOperations;

Task #3 - how to test the Command Marker

Ok, so the first thing I want to do is make sure my Command Marker calls the proper command implementation methods. First up, we widen the reference to our CoffeescriptOperations variable:

@Reference CoffeescriptOperations operations;

Then, we write a method that uses Mockito to mock calls to the operations object. This is the test class:

package org.sillyweasel.addons.coffeescript;

import junit.framework.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;

public class CoffeescriptCommandsTest {

  private CoffeescriptCommands commands;

  @Before
  public void setUp() {
    commands = new CoffeescriptCommands();
    commands.operations = mock(CoffeescriptOperations.class);
  }

  @Test
  public void testIsPluginInstalled() {
    when(commands.operations.isPluginInstalled()).thenReturn(false);
    assertThat(commands.isCoffeescriptInstalled(), is(false));
  }

  @Test
  public void testIsCoffeescriptSetupAvailable() {
    when(commands.operations.isSetupCommandAvailable()).thenReturn(true);
    assertThat(commands.isSetupCommandAvailable(), is(true));
  }

  @Test
  public void testIsCoffeescriptRemoveAvailable() {
    when(commands.operations.isPluginInstalled()).thenReturn(true);
    assertThat(commands.isCoffeescriptInstalled(), is(true));
    verify(commands.operations, times(1)).isPluginInstalled();
  }

  @Test
  public void testInstallCoffeeScript() {
    commands.setup("foo", "bar", true);
    verify(commands.operations).setup("foo", "bar", true);
  }

  @Test
  public void testRemoveCoffeeScript() {
    commands.remove();
  }

  @Test
  @Ignore
  public void testFileSets() {
    Assert.fail();
  }
}

See how I'm using some Hamcrest matchers in there? I like assertThat(..., is()) syntax.

So now we know that our command marker is actually calling the delegate methods when it is being invoked. And we're on our way, able to mock anything we want to, provided we widen the scope a bit from a 100% internal private member variable.

In our next post I'll show you how to test the actual XML configuration. That one is going to be rather large so I'll separate it into another one.

Tuesday
May082012

Didn't make it to Philly Emerging Tech? Watch the sessions online!

Let's say you didn't get a ticket to the Emerging Technologies for the Enterprise conference, but you really, really wanted to go.

We're happy to oblige.

Visit our Emerging Technologies 2012 Screencast microsite. RSS readers, subscribe here.

Sunday
May062012

Don't call names - Roo's Tag with a tag problem

Eureka, ish..

I was trying to find time to debug a problem that's been on my backlog for a while, and someone else ran into it. A reader over on the Manning Spring Roo in Action forum was getting hit with a 400 invalid request error when trying one of the samples from the book (the many-to-many example).

A while back, I ran into this problem - my m:m arrangement was between Courses and Tags, and the field name of the actual tag in the Tag entity was called 'tag'. When I did this, I got the same error. Someone hit it in the forums, we renamed it to 'name' and committed our code, and I forgot about it for a while.

My theory was that the type converters somehow were generated wrongly. Nope, I have a " target="_blank" class="offsite-link-inline">JUnit test to review that does it properly, so that's not the problem.

Actually, the problem is with the type conversion, but it's not the fault of the Roo code for type converters (to my knowledge so far). They are just doing what they are supposed to do.

The REAL Problem

First of all, my theory on the bad type converters was wrong. I proved it by writing a set of tests to make sure I a) understood the true workings of the generated type converters and b) that they worked. In the next edition of Roo in Action I think I need to devote some time to these. See the attached sample at the end to see my tests. Here is how they work (example - the tag):

Type Conversions

  • Convert an object -> a single line String representation as a default label for a given dropdown entry. (converterFactory.getTagToStringConverter())
  • Hydrate a Tag via a String representation of the key (converterFactory.getStringToTagConverter())
  • Hydrate a Tag via a primary key value (converterFactory.getIdToTagConverter())

All of these work just fine when tested individually, which they should.

The Tag to String converter is used in dropdowns for the many-to-many relationship, so that when the tag editing screen (which owns the relationship) allows a user to select one or more courses, it just shows the field values separated by spaces.

So, now what?

Spring MVC creates forms with a set of assumptions, including the fact that the form will be of a certain bean instance root. This is provided by specifying the modelAttribute in Spring MVC, but since Roo does this for us, we aren't in charge of that in a scaffolded UI.

Here is the create.jspx file for reference.

The scaffolded value is generated by create.tagx, which uses this code:

<form:form action="${form_url}" method="POST"
   modelAttribute="${modelAttribute}" enctype="${enctype}" 
   onsubmit="${jsCall}"> ...

The essential problem I think we're running into with the samples the way they are put together is:

  • The "bean context" / model attribute name of the form being submitted is expected to be 'tag'
  • The form is submitting fields in this form with names such as description, id, and also tag
  • Spring MVC is spitting nails on the fact that you have tag.tag in your request. It wants to convert the nested tag into another Tag object, by hydrating it (probably to support recursive relationships or something). Well, it takes your String for the tag label and tries to take it through idToTagConverter() which fails on a numeric parse.

How to fix For now, avoid using the entity name as a field name in a given entity. I need to do more extensive testing to see why this is happening. This is something that I should know by heart, I guess, but I've been wrestling with other areas of Java and Roo and haven't been as connected to the web forms as I used to be.

If anyone wants to take this on, I have posted the code sample in the original Manning thread (look on the second page) with the junit tests included. Just create a course, then create a tag with the 'tag' field of any alpha value.

[b]The sample test[/b]

I created a spring junit test to check my assumptions (I imported hamcrest-all for matching support to make the assertions readable quickly, the pom includes org.hamcrest:hamcrest-all:1.1).

Monday
Feb272012

Quizzo in Roo case of the Mondays. Or, how you can be a JPA doofus 

This morning I took a look at my schema, and started to cry. Well, not really, but in that way people say LOL, I say COL. Here was my original schema (click to view the picture in full resolution):

Wait, this guy knows JPA? Sure he does!

Now, I know some of you have my back here. You say, "look, JPA can be confusing, you have to work with it." The thing is, I wrote a bloody chapter on JPA relationships, I teach JPA, my head just wasn't in the game, OK?

Just kidding. You know by now I'm razzing you all...

Anyhoo, I didn't bother taking any time to pay attention to my relationships (sounds like somebody's going to have cold soup for dinner). Turns out, I was abusing my @JoinColumn and mappedBy settings.

How to do a bi-directional one-to-many relationship

Do it up, Rimp. Here's how. First off, you need to define your one-sided entity and allow it to contain your Set (don't get me started about alternative options for collection types, that'll take us all day):

@RooJavaBean
@RooToString
@RooJpaEntity
public class Quiz {

    @NotNull
    @Size(max = 200)
    private String title;

    @NotNull
    @Size(max = 500)
    private String description;

    @OneToMany(mappedBy = "quiz",
        cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<Question> questions = new HashSet<Question>();

}

Now, I know we're all adults in here, so that's why I put the harder stuff in the cabinet. You see, there are several key items of critical information here:

  • The collection is of type Set, and I'm creating a prototype (for new entities before I save them) of the real type of HashSet. Don't assume they will be HashSet instances when you fetch them again, they likely could be proxies that hydrate when you touch them (wear coding gloves).
  • I've decided to use the cascade option to tell JPA to cascade changes to the questions if I persist or update (merge) the quiz.
  • The mappedBy element is all-important. This tells us that the property pointing back to our Quiz instance in the class named Question will be called quiz. This establishes what side owns the relationship. As one of my students once said, "if you see mappedBy, it means this class does NOT own the relationship." Good rule to live by.

Whew! I'm feeling weak and dizzy. A lot of information hiding in there. Let's look at the Question class for the other side of this thing:

@RooJavaBean
@RooToString
@RooJpaEntity
public class Question {
  @ManyToOne
  @JoinColumn(name = "quiz_id")
  private Quiz quiz;
}

Wow, that's it??? Huh. Ok, well erm let's give this a shake. This class is in charge of the relationship, meaning that if it is touched, it will handle the insert or update of the foreign key. So:

  • The quiz variable name matches the mappedBy element in the Quiz table. Think of this as the role of the relationship. You may use the same table twice with different roles (think sourceAccount, destinationAccount and you get the idea. They would each have different @OneToMany relationships with different mappedBy elements, thereby different roles and variables.
  • The foreign key column that handles the database relationship is called quiz_id, and so we'll get a table called Question with a foreign key in it called quiz_id

Easy, peasy! Now, if you look at the relationship between Question and Quiz in the image above, it looks like there are three tables. But in the one below, the relationships are all simplified. That's because I paid attention to @JoinColumn and mappedBy. Click the image to see the full copy:

Mmmm... Low fat data models. Delicious and 1/2 the tables of your normal schema.

What else did I change recently?

I also got the state machine 99% working, with just a hitch on the test that determines whether the quiz is over or not. I'll work on that one tonight, now that I can actually understand my data model without getting sick to my stomach.

I moved the QuizRunState tracking state into memory, as it was failing me. Oh, one more thing - don't assume that IntelliJ catches Java Language assert statements. I was guarding my code with assertions to catch any invalid states in the state machine. Turns out, Maven turns on assertion checking when testing but IntelliJ does not. I'm not talking about JUnit assertions, but the Java System assertion language statement, such as I use in this fragment of QuizRunStateMachineInMemory:


  @Transactional
  public void startQuiz() {
    assert (runState == QuizRunState.NOT_STARTED);
    runState = QuizRunState.IN_PROGRESS;
  }

Oh, it burns us baggins! To fix this, you edit your JUnit runner and add the -ea -esa flags to your vm settings. And, ladies and gentlemen, that was the reason I was getting errors in my Maven code that weren't showing up in IntelliJ. Just sayin'

See ya tomorrow (or maybe later tonight).

Monday
Feb202012

Quizzo repo public

Against my better judgement, I made the repo public, so you can see my "progress" warts and all. Remember, this is a side effort for me and just for fun, so it's not going to look super-well organized, but here it is.

github.com/krimple/quizzo