Mapping a Flat Data Model to a Relational Data Model Using Dozer and Proxies

Introduction

I’m working for an insurance company which uses the AL3 form standard along with the Acord XML standard for the insurance business.  Acord has created a mapping spreadsheet which maps AL3 fields to Acord XML fields.  This is great because it allows us to communicate business-to-business with our carriers.  But there is a problem with the mapping.  [no proprietary information is shared in this article]

You map MailingAddressStreet to /customer/address[1].street

Do you see the problem?  Customer can have zero to many addresses of types mailing and home.  This means that an address has a code ‘H’ or ‘M’ to designate which one it is.  This also means that a customer’s lists of addresses must have an Address with a code of ‘M’ at index 1 for this mapping to work.  And this is how the current version of our software works.

The scale of our system is small right now, so the problem is not yet apparent, but it must scale to a huge size by the end of our implementation.  I believe the chances of a vulnerability are high with this mapping design, and so I’ve created an alternative.

Technology

For my proof of concept I’ve chosen a simplified technology stack to test my design using Spring MVC, Spring JPA, Hibernate, SQL Server, JSP, and Dozer. You will recognize the first five, but you may not recognize the last.  Dozer is a Java Bean-To-Bean mapping API which we chose specifically for AL3 to Acord XML mapping.  It is elegantly simple.

Using an XML file (or annotations) I can map each field of a DTO (Data Transfer Object) to a JPA Entity with a single command:  mapper.map(DTO,Entity) and back mapper.map(Entity, DTO).  It’s a marvelous time saver.  Mapping from the web to the database is painstaking, low-level code made easy with Dozer and JPA.  I’m 100% sold on these technologies.

Dozer’s Solution

The Dozer folks have proposed exactly what the Acord folks propose

dozer-index-mapping

Look familiar?  In this example userName1 must be in the array first.  But what if you are using an ArrayList, which is unordered?  What if your business logic orders them differently?

My goal is to solve the the AL3 to Acord XML multiple Address problem and make the solution full proof while continuing to take advantage of the Dozer technology.

My Solution

My team has been looking at problems like this for many months, and one day a member of my team proposed this:  make the JPA Entity mirror the DTO on the surface and break up the data into objects underneath.

This means if the DTO bean has methods called setMailingStreet and getMailingStreet then the Entity, even though it does not having these properties, should have the same methods.  Dozer looks at the public getters and setters to map.  It doesn’t care what you do with the data once they are called.  We would be transiently augmenting the entity which simply means that we would add these new methods to the entity and they will not be persisting anything.  We do this with the @Transient annotation on the methods.  These transient methods are proxies for a second layer of conversion.

The Code

To use this technology stack, there is much configuration to do and I will not cover that.  Instead, I will you give you the essential snippets to demonstrate this design starting with the form.

The app is simple.  We have a Customer and he/she has Addresses.  I want to present the customer with only two kinds of addresses:  home and mailing.  In the database, Customer has a relationship with Address that will allow for zero to many addresses per customer, and the Entity is the same way.  To the user, it’s all one record.  To the database, it is many records;  one-to-many.

The Customer app is simply a CRUD (Create Read Update Delete) app.

The form is very simple and looks like this

customer-form

This maps to a DTO bean.  Here’s a snippet to give you the idea.

public class CustomerDTO
{
	private int id;

    private String firstName;

    private String middleInitial;

    private String lastName;

    private Integer homeAddressId;

    private String homeStreet;

    private String homeCity;

    private String homeState;

    private String homeZip;

    private Integer mailingAddressId;

    private String mailingStreet;

    private String mailingCity;

    private String mailingState;

    private String mailingZip;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public void setFirstName(String firstName)
    {
        this.firstName = firstName;
    }

    public String getMiddleInitial()
    {
        return middleInitial;
    }

    public void setMiddleInitial(String middleInitial)
    {
        this.middleInitial = middleInitial;
    }

    public String getLastName()
    {
        return lastName;
    }

    public void setLastName(String lastName)
    {
        this.lastName = lastName;
    }

    public String getHomeStreet()
    {
        return homeStreet;
    }

    public void setHomeStreet(String homeStreet)
    {
        this.homeStreet = homeStreet;
    }
...

As you can see, the DTO maps precisely to the form.  However, the Customer entity bean only has firstName, middleInitial, lastName, id, and a list of Address beans.  This is not a complete mapping.

The Dozer mapping file looks like this.

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 	xsi:schemaLocation="http://dozer.sourceforge.net           http://dozer.sourceforge.net/schema/beanmapping.xsd">
	<mapping>
		<class-a>com.mycompany.customer.dto.CustomerDTO</class-a>
		<class-b>com.mycompany.customer.domain.model.Customer</class-b>
	</mapping>
</mappings>

The way Dozer works is that it inspects each object for matching getters and setters and it transfers the data for the ones which are the same.  Anything that doesn’t match is ignored.  The mapping can be done explicitly to resolve when the field names do not match, but what do you do when the field just doesn’t exist as in the case of the address fields?

That’s when my proxy pattern comes into play.  I add those fields explicitly to the Customer bean.  I take each getter and setter from the DTO and add them to the Customer bean with the @Transient annotation so JPA knows not to mess with them.

This is where it gets tricky because we have to take a zip or a street or a state or a city and put it into an Address object with a matching code.  For example, a mailing zip needs to be in an Address object with a code of ‘M’ for mailing.  In this application there can be only one mailing address for a customer.  That takes a bit of doing.  Here’s one way of doing it.

@Transient
public String getMailingZip()
{
	Address mailingAddress = null;
	if (addressList != null)
	{
		for (Address address : addressList)
		{
			if (AddressCode.MAILING.equals(address.getCode().trim()))
			{
				mailingAddress = address;
			}
		}
	}
	if (mailingAddress != null)
	{
		return mailingAddress.getZip();
	}
}

@Transient
public void setMailingZip(String mailingZip)
{
	Address mailingAddress = null;
	if (addressList == null)
	{
		mailingAddress = new Address();
		addressList = new ArrayListAddress>();
		addressList.add(mailingAddress);
	} else
	{
		for (Address address : addressList)
		{
			if (AddressCode.MAILING.equals(address.getCode().trim()))
			{
				mailingAddress = address;
				mailingAddress.setZip(mailingZip);
			}
		}
	}
	mailingAddress = new Address();
	mailingAddress.setCustomerId(this);
	mailingAddress.setCode(AddressCode.MAILING);
	mailingAddress.setZip(mailingZip);
	addressList.add(mailingAddress);
     }

I’ll step you through it.

  1. To get a mailingZip we first need to find the mailing address.  The Customer bean has a list of of Address beans.  In this case there is one with an address code of ‘H’ and one with ‘M’.  We want ‘M’ for mailing.
  2.  Once we have that object we can get the zip and return it.  Dozer thinks there really is a mailingZip property in Customer and so it maps it.

To set a zip we do a similar thing.

  1. We make sure that there is an addressList.  If this is a new customer there may not yet be one.  If there isn’t, then we create a new one.
  2.  Then we create a new Address, set the zip and add it to the list.
  3.  If there is already a list then we search for the ‘M’ code.  If we find it, then we set the zip, if we don’t then we create a new Address with the ‘M’ code and set the zip.

Dozer will call all of the sets or gets depending on which is the target and which the source.  Here is example of how to transfer from the DTO to the entity bean for editing.

To edit a customer, the method makes the customerDTO the source and the empty customer class as the destination if it’s a new customer, an existing customer if it’s an existing one.

New Customer

customer = mapper.map(customerDTO, Customer.class);

Existing Customer

customer = mapper.map(customerDTO, customer);

However, if we need to display a customer, we need to do the opposite.

CustomerDTO dto = mapper.map(customer, CustomerDTO.class);

What all of this means is that there are two mappings happening here.  Dozer maps to and from the proxy methods and then the proxy methods are smart enough to map to the correct Address objects.

As a bonus, I’ll show how I can further abstract the proxy mapping so that all of that code doesn’t have to appear for every method.  I’m sure I’ll eventually make it so that it can be used for any such entity, but this is good enough for now.

@Transient
public String getMailingZip()
{
	return(String)getAddressPart(AddressCode.HOME,"getZip",getAddressByCode(               AddressCode.MAILING));
}

@Transient
public void setMailingZip(String mailingZip)
{
	this.setAddressPart(AddressCode.MAILING, mailingZip, "setZip",String.class);
}

@Transient
private Address getAddressByCode(String code)
{
	if (addressList != null)
	{
		for (Address address : addressList)
		{
			if (code.trim().equals(address.getCode().trim()))
			{
				return address;
			}
		}
	}
	return null;
}
@Transient
private Object getAddressPart(String code, String methodName, Address addr)
{
	Class
<Address> addrClass = Address.class;
	Method getMethod = null;
	Object rtn = null;
	if (addr == null)
	{
		addr = new Address();
	}
	try
	{
		getMethod = addrClass.getMethod(methodName, new Class[]{});
		rtn = getMethod.invoke(addr);
	} catch (NoSuchMethodException | SecurityException | IllegalAccessException  IllegalArgumentException | NullPointerException | InvocationTargetException e)
	{
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return rtn;
}
@Transient
private void setAddressPart(String code, Object addressPart, String methodName, Class dataType)
{
	Class
<Address> addrClass = Address.class;
	Method setMethod = null;
	if (addressList == null)
	{
		addressList = new ArrayList
<Address>();
	}
	try
	{
		setMethod = addrClass.getMethod(methodName, new Class[]{ dataType});
		for (Address existingAddr : addressList)
		{
			if (code.equals(existingAddr.getCode().trim()))
			{
				setMethod.invoke(existingAddr, addressPart);
				return;
			}
		}
		Address newAddr = new Address();
		newAddr.setCustomerId(this);
		newAddr.setCode(code);
		setMethod.invoke(newAddr, addressPart);
		addressList.add(newAddr);
	} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
	{
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return;
}

First, look at the getter and setter for zip.  I’ve reduced it to one unique line of code each.  All of the logic is now in getAddressByCode, getAddressPart, and setAddress part.  So we have a look up, a getter, and a setter which can be used for any part of any address.

Alternative

As I wrapped up this project, I thought of another possible way to do this.  Dozer is an extensible framework.  It allows you to add custom converters to the mapping that might do the same thing as what I’ve done here.  I like the notion of a customer converter because the DTO doesn’t need to know anything about the entity bean and vice versa.  My predecessor used custom converters, but they ended up doing the entire conversion, nearly removing Dozer from the picture completely.  I want Dozer to do the part it can do and have my solution do the rest. That may end up being a cleaner solution with a clearer separation of concerns.  You may see another blog post on this.  But for now, my company has a working solution and that may be all they care about.

Conclusion

To summarize, we’ve tricked Dozer into believing that it can map CustomerDTO with a Customer entity bean by adding transient proxy methods to the bean.  We’ve created internal mapping to shift the form data into a relational model between Customer and Address. We’ve secured the data by avoiding the deep indexing method proposed by AL3 and Dozer.

If you have suggestions or questions, please don’t hesitate to leave your comments.  I am, after all, just a Regular Average Java Programmer (RAJP).