March 11, 2013

JAXB Unmarshalling To a Sorted Map

Introduction

While using JAXB for my last project, I came across the need to access a list of data in two ways.  First, the data was to be accessed as a sorted list with the sort order being determined by the /Book[@SortOrder] attribute. Second, the data was to be access as a map, supplying a key value from /Book[@Id] to get a specific book by key. With these two requirements, a LinkedHashMap object is clearly the way to go. Book objects are added to the LinkedHashMap in /Book[@SortOrder] order and /Book[@Id] is the map key. My initial thought was to let JAXB unmarshal the XML into a regular List then as a post processing step I would reorder the list and create a map.  But the more I thought about this the more I didn't like this solution.  Since JAXB is doing the work of unmarshalling the XML, I figured JAXB should be able to put it into a LinkedHashMap like I wanted. The purpose of this article is to demonstrate how to get JAXB to create a LinkedHashMap for you so you can access a list of data in both by key and as a correctly sorted list.

The XML

 Let's first consider a simple XML document to demonstrate what's needed.  Below is sample XML document representing search results for books. The list of books has a [@SortOrder] and an [@Id].  The application will need to be able to display the books in the correct sort order but also be able to lookup books quickly by id.
<SearchResults>
    <Books>
        <Book Id="794N-98" SortOrder="5">
            <Title>Open to the Sky</Title>
        </Book> 
        <Book Id="998X-78" SortOrder="2">
            <Title>Mars, Friend or Foe?</Title>
        </Book>
        <Book Id="445M-09" SortOrder="3">
            <Title>Jumping Beans Jump</Title>
        </Book>  
        <Book Id="002I-11" SortOrder="1">
            <Title>Little Worms Go To School</Title>
        </Book> 
        <Book Id="951G-42" SortOrder="4">
            <Title>America, The Early Years</Title>
        </Book> 
    </Books>
</SearchResults>


The Classes

In order to get JAXB to create a LinkedHashMap for you automatically, you will need need the following classes, with the BooksAdapter class one really doing all of the work for you:

SearchResults.java

Unmarshalled object for the <SearchResults> tag
@XmlRootElement(name="SearchResults")
public class SearchResults 
{
	@XmlElement(name="Books")
	@XmlJavaTypeAdapter(BooksAdapter.class)
	private Map<String, Book> books;
	public Map<String, Book> getBooksMap() {
		return books;
	}
	
	public List<Book> getBooksList() {
		return new ArrayList<Book>(books.values());
	}
}

Books.java

Unmarshalled object for the <Books> tag. This is initially needed by JAXB to convert all of the <Book>  elements into a list.  This class is then later used by BooksAdapter to convert it into a LinkedHashMap.
@XmlRootElement(name="Books")
public class Books 
{
	private List<Book> books;
	
	@XmlElement(name="Book")
	public List<Book> getBooks() {
		return books;
	}
	
	public void setBooks(List<Book> books) {
		this.books = books;
	}
}

Book.java

Unmarshalled object for the <Book> tag
@XmlRootElement(name="Book")
public class Book 
{
	@XmlAttribute(name="SortOrder")
	private Integer sortOrder;
	public Integer getSortOrder() {
		return sortOrder;
	}

	
	@XmlAttribute(name="Id")
	private String id;
	public String getId() {
		return id;
	}
	
	@XmlElement(name="Title")
	private String title;
	public String getTitle() {
		return title;
	}
}

BooksAdapter.java

An XmlAdapter implementation which converts the Books object into a LinkedHashMap object where the Book objects are put into the map by key /Book[@Id] and in the order specified by /Book[@SortOrder]. 
public class BooksAdapter extends XmlAdapter<Books, Map<String, Book>> {
	
	@Override
	public Map<String, Book> unmarshal(Books books) throws Exception {
		Collections.sort(books.getBooks(), new BookComparatorBySortOrder());
		Map<String, Book> map = new LinkedHashMap<String,Book>();
        for (Book book : books.getBooks()) {
            map.put(book.getId(), book);
        }
        return map;
	}

	@Override
	public Books marshal(Map<String, Book> map) throws Exception {
		Books books = new Books();
		books.setBooks(new LinkedList<Book>(map.values()));
		return books;
	}
}

class BookComparatorBySortOrder implements Comparator<Book> {
	@Override
	public int compare(Book o1, Book o2) {
		return o1.getSortOrder().compareTo(o2.getSortOrder());
	}
	
}

 

Testing

Testing this is pretty easy, just supply the XML document and have JAXB do it's thing. Here is a unit test:

SearchResultsTest.java 

public class SearchResultsTest 
{
	private SearchResults searchResults; 
	
	@Before
	public void before() throws Exception 
	{
		// Read file from classpath
		String filename = "/SearchResults.xml";
    	InputStream istream = getClass().getResourceAsStream(filename);
    	assertNotNull(String.format("Cannot find classpath resource \"%s\"", filename), istream);
    	
    	// Prepare JAXB objects
		JAXBContext jc = JAXBContext.newInstance(SearchResults.class);
		Unmarshaller u = jc.createUnmarshaller();

		// Prepare the input
		InputSource isource = new InputSource(istream);

		// Do unmarshalling
		searchResults = (SearchResults)u.unmarshal(isource);
		
        assertNotNull(String.format("Unmarshal returned null for SearchResults object"), searchResults);
        
        istream.close();
        istream = null;
	}
	
	@Test
	public void testBookFindById() 
	{
		
		Map<String, Book> books = searchResults.getBooksMap();
		assertNotNull(books);
		assertEquals(5, books.size());
		
		assertEquals("Little Worms Go To School",books.get("002I-11").getTitle());
		assertEquals("Mars, Friend or Foe?", 	 books.get("998X-78").getTitle());
		assertEquals("Jumpping Beans Jump", 	 books.get("445M-09").getTitle());
		assertEquals("America, The Early Years", books.get("951G-42").getTitle());
		assertEquals("Open to the Sky", 		 books.get("794N-98").getTitle());	
	}
	
	@Test
	public void testBookSorted() 
	{
		
		List<Book> books = searchResults.getBooksList();
		assertNotNull(books);
		assertEquals(5, books.size());
		
		assertEquals(new Integer(1), books.get(0).getSortOrder());
		assertEquals(new Integer(2), books.get(1).getSortOrder());
		assertEquals(new Integer(3), books.get(2).getSortOrder());
		assertEquals(new Integer(4), books.get(3).getSortOrder());
		assertEquals(new Integer(5), books.get(4).getSortOrder());		
	}

}

Download

You can download a Maven project of this from sourceforge:

http://ferris.cvs.sourceforge.net/viewvc/ferris/ferris-jaxb-sortedmap/

November 29, 2012

Maven dependency unpacking filtering in custom assemblies

Introduction

I have been using Maven assemblies for a while to do some relatively simple building of artifacts which Maven does not build out of the box.  Assemblies are quite powerful and can do a lot for you. I ran into a problem with dependency unpacking. I needed some of the unpacked files to be filtered so as to replace ${variablename} inside the files but found this feature not supported.  The purpose of this article is to demonstrate how dependency unpacking filtering can be done when using custom Maven assemblies.

Of course remember that the plugins attached to a particular <phase> are executed by Maven in the order you have them in our pom - put the plugins in the wrong order and you won't get the right results.

1. Use maven-dependency-plugin to get the dependency unpacked.

First  you want to use the maven-dependency-plugin to unpack your dependency. Unpack inside the /target directory for use later.  The XML below shows configuration to do this.

1:  <plugin>  
2:    <groupId>org.apache.maven.plugins</groupId>  
3:    <artifactId>maven-dependency-plugin</artifactId>  
4:    <version>2.6</version>  
5:    <executions>  
6:     <execution>  
7:      <id>unpack-dependencies</id>  
8:      <phase>prepare-package</phase>  
9:      <goals>  
10:       <goal>unpack-dependencies</goal>  
11:      </goals>  
12:      <configuration>     
13:       <includeArtifactIds>SomeArtifactId</includeArtifactIds>         
14:       <outputDirectory>${project.build.directory}/SomeArtifactId</outputDirectory>             
15:      </configuration>  
16:     </execution>  
17:    </executions>  
18:   </plugin>  


In the code above:

Line #8 links this plugin configuration to the prepare-package phase, which is typically what you will want to do since you need to unpack your dependencies before the package phase runs your custom assembly

Line #10 sets the goal to unpack-dependencies, which of course means the dependency will be unzipped.

Line #13 is the <includeArtifactIds> tag which can take a comma-separated list of <dependency><artifactId> id values. I typically like to use only 1 value though and if I need to unpack multiple dependencies I create multiple plugin configurations.

Line #14 is the <outputDirectory> tag and this specifies where the dependency is unzipped.  Typically, just dump it into some /target subdirectory.

2. Use maven-assembly-plugin to build your assembly.

Next you will need to configure the maven-assembly-plugin to execute during your build. Of course put this plugin configuration AFTER the maven-dependency-plugin configuration so Maven executes them in the right order. The XML below shows configuration to do this:

1:        <plugin>  
2:          <artifactId>maven-assembly-plugin</artifactId>  
3:          <executions>  
4:            <execution>  
5:              <configuration>  
6:                <descriptors>  
7:                  <descriptor>my_assembly.xml</descriptor>  
8:                </descriptors>  
9:              </configuration>            
10:              <id>create_directory</id>  
11:              <phase>prepare-package</phase>  
12:              <goals>  
13:                <goal>single</goal>  
14:              </goals>  
15:            </execution>  
16:          </executions>  
17:        </plugin>  


In code above:

Line #7 says to use the assembly defined in your my_assembly.xml file.

Line #11 links this plugin configuration to the prepare-package phase.  This is the same phase the maven-dependency-plugin configured above is linked to. Maven will run this maven-assembly-plugin configuration after the maven-dependency-plugin configuration as long as have them in that order in the pom.

The my_assembly.xml file is the configuration for your custom assembly.  To work with the unpacked dependency files, you refer to them just like you would any other file.  The XML below shows configuration to do this.

1:  <fileSet>  
2:        <directory>${project.build.directory}/SomeArtifactId</directory>  
3:        <outputDirectory>/where/to/put/it/in/your/assembly</outputDirectory>  
4:        <includes>  
5:          <include>file-to-be-filtered.txt</include>          
6:        </includes>  
7:        <filtered>true</filtered>  
8:  </fileSet>  


As you can see, you can use the <filtered> option to make sure the files are filtered. Of course, use multiple configurations if you need to filter some files but not others.

In the code above:

Line #2 says to use the files which were unpacked by the maven-dependency-plugin

Line #3 says where to put the files in your assembly

Line #5 says what files are to be included.

Line #7 says to filter the files.  This will of course replace the ${variablename} strings

Summary

That's it, enjoy

July 06, 2012

SVN Username Mysteriously Changes on Windows

Introduction

While using SVN, I recently noticed my commits to the repository were no longer using my computer login username.  Somehow, something happened to tell SVN to use a different username.  If you are using Windows for you development environment and you have run into this issue, this article will give you hints on where to look to fix it.

Subversion's "Application Data"

I am running the Window's SVN client SlikSVN v1.6.1.  After running the Maven release plugin on my project, I noticed commits to the SVN repository were no longer using my computer login username, but were instead using the username associated with running the Maven release plugin.  Since not all users are given Nexus permissions to write releases, the Maven release plugin needs to be executed with a different username.  SlikSVN somehow "remembered" this username and all commits to the SVN repository after that were using it.  This is of course a major problem because looking back through the SVN history it is not possible to determine who really made the changes.

After searching online for this problem, I spent some time running through the "Application Data" directory.  I eventually found this directory:
XP: "C:\Documents and Settings\[username]\Application Data\Subversion\auth\svn.simple"
7: "C:\Users\[username]\AppData\Roaming\Subversion\auth\svn.simple"
Inside this directory is what looks like a randomly generated file:
42a1ce589862a9b8acde6133f937c22d
With contents that looks like this:
K 8
passtype
V 8
wincrypt
K 8
password
V 280
[SOME_ENCRYPTED_PASSWORD]
K 15
svn:realmstring
V 28
<http://bjcesinf03:80> crowd
K 8
username
V 14
[SOME_SVN_USERNAME]
END

The [SOME_SVN_USERNAME] was my problem. 

The solution is to delete this file.

Summary

If you are running Windows and your SVN username mysteriously becomes something else when performing commits to the repository, try going to this directory:
 "C:\Documents and Settings\[username]\Application Data\Subversion\auth\svn.simple"
and delete the randomly generated file in this directory which contains the username.

Enjoy!