Monday, 15 April 2013

Just What Are Spring 3.2 Matrix Variables? - Part 2: The Code

My last blog on Spring's support for Matrix Variables concentrated on explaining what they were and why you'd want to use them. Having sorted out the what and the why, this blog is all about the how and how you use them. I also gave several examples of Matrix URIs and so, it seems good idea to demonstrate some code that processes a couple of them.

The examples were:

http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.40,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07

http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.90,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07/account;name=roger;number=105;location=stoke-on-trent,uk

As you'd expect, in writing code to handle Matrix Variables, the Guys at Spring are building upon the existing Spring MVC framework by introducing the new @MatrixVariable annotation. This is used to annotate request handler method arguments so that Spring can inject the relevant bits of the matrix uri. @MatrixVariable has four arguments: value, defaultValue, pathVar and required, all of which are fully explained in Springs javadocs.

And so to some code... If you remember in my last blog on this subject, the scenario I chose was one that deals with processing a bunch of share/stock prices and the sample application, available on Github, takes a Matrix URI, chops it up and adds it to a Model for a JSP to display.

In writing the code, the first thing to do is to create a new controller to process the URIs...

@Controller
@RequestMapping
(value = "/matrixvars")
public class MatrixVariableController {

 
private static final Logger logger = LoggerFactory.getLogger(MatrixVariableController.class);
}

In the code I've added a class level @RequestMapping annotation, which contains the first chunk of my URIs: matrixvars. This is a useful thing to do as it directs all URIs that contain the value 'matrixvar' as the first path element to this controller and saves a lot of duplication.

The next thing to do is to add some code to this class that deals with the first URI:

http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.40,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07

The first request handler method is:

  @RequestMapping(value = "/{stocks}", method = RequestMethod.GET)
 
public String showPortfolioValues(@MatrixVariable Map<String, List<String>> matrixVars, Model model) {

   
logger.info("Storing {} Values which are: {}", new Object[] { matrixVars.size(), matrixVars });

    List<List<String>> outlist = map2List
(matrixVars);
    model.addAttribute
("stocks", outlist);

   
return "stocks";
 
}

 
private List<List<String>> map2List(Map<String, List<String>> stocksMap) {

   
List<List<String>> outlist = new ArrayList<List<String>>();

    Collection<Entry<String, List<String>>> stocksSet = stocksMap.entrySet
();

   
for (Entry<String, List<String>> entry : stocksSet) {

     
List<String> rowList = new ArrayList<String>();

      String name = entry.getKey
();
      rowList.add
(name);

      List<String> stock = entry.getValue
();
      rowList.addAll
(stock);
      outlist.add
(rowList);
   
}

   
return outlist;
 
}

Looking at the @RequestMapping annotation you can see that I've assigned it a value of /{stocks}. This, when combined with the class level @RequestMapping annotation, will instruct Spring to map any matching requests to this method. The text inside the curly braces, {stocks}, indicates that this part of the URI can be parsed and injected into the appropriate method argument.

Next, take a look at the @MatrixVariable annotation. This sits neatly in front of the argument into which I want the stock data injected; however, the slightly tricky thing here is getting the argument type right. If you get this wrong then you'll simply get a ClassCastException when you try to use your data. When the input data is of the form:

A=B,C,D

or

A=B,C,D;W=X,Y,Z

...then the type is Map<String,List<String>>, where the keys are A and W and their respective values are B,C,D and X,Y,Z.

Hence, given the URI above, the map argument will contain....

{BT.A=[276.70, +10.40, +3.91], AZN=[236.00, +103.00, +3.29], SBRY=[375.50, +7.60, +2]}

That's the important bit over with, the rest of the method is very straight forward in that is simply converts the input map into a list and adds it to the model for the JSP (not shown here) to display. Note that this isn't very useful code, so don't pay that much attention to it and besides I'm not fond of embedding collections within collections - it doesn't seem like a good idea.

Moving on, I'll now take a look at the next URI. Notice that I've purposely made this similar to the first, with the only difference being the addition of the user's account details:

http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.90,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07/account;name=roger;number=105;location=stoke-on-trent,uk

This URI is mapped to the following method:

  @RequestMapping(value = "/{stocks}/{account}", method = RequestMethod.GET)
 
public String showPortfolioValuesWithAccountInfo(@MatrixVariable(pathVar = "stocks") Map<String, List<String>> stocks,
     
@MatrixVariable(pathVar = "account") Map<String, List<String>> accounts, Model model) {

   
List<List<String>> stocksView = map2List(stocks);
    model.addAttribute
("stocks", stocksView);

    List<List<String>> accountDetails = map2List
(accounts);
    model.addAttribute
("accountDetails", accountDetails);

   
return "stocks";
 
}

In this case the full path description is /matrixvars/{stocks}/{account}. I guess that this simply tells Spring to look out for /matrixvars, followed by a '/' followed by anything, followed by a '/', followed by anything, when it does its mapping.

In this case there are two @MatrixVariable annotations to which I've added the annotation's pathVar argument supplying values stocks and accounts. These neatly label where the matrix variable values need injecting into the method arguments.

The final point to remember is that Matrix Variable are incredibly flexible; there are another three arguments to the @MatrixVaraible annotation that I've not considered here; however, the general procedure is the same in every case: take the URI, figure out what the different matrix variables are, design a request handler and map the URI's matrix variables to your methods arguments - taking care to ensure that you get the argument type correct.


The full sample code for this blog is available on Github: https://github.com/roghughe/captaindebug/tree/master/spring-3.2


No comments: