In this page we will go through a walk-through example to see how PODAM works in detail. We will explore how PODAM deals with the following scenarios:
Let's start with the Domain Model of a simple graph of objects. The Model depicts a very simple scenario with the root element being a Client. A Client can place many Orders, each Order might have many OrderItems, each OrderItem defines an Article.
A Client can also have Addresses and BankAccounts.
An address is located in one Country.
This Domain Mode, although simple, demonstrates the full potential of PODAM, such as the ability to automatically fill graphs of objects and container-like relationships (such as Collections and Maps). It also shows that PODAM is capable of filling immutable-like POJOS (such as those with final attributes and a constructor, but not setter methods).
Let's start with the definition of the third-level and second-level domain objects.
The Country Domain Object is an immutable-like class. From a business perspective, a Country is a static reference data. Ideally I'd like to create Country objects once (say at startup) and never change them. Very occasionally a Country description of code might change.
Please look at the constructor carefully. First, since this is an immutable-like class, if I want PODAM to create an instance of this class and fill it with values, I must annotate the constructor with the @PodamConstructor annotation.
Secondly, I want all my country code to be maximum 2 characters in length. So I annotated the countryCode parameter with the @PodamStringValue annotation indicating a value of 2 for the length.
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; import uk.co.jemos.podam.annotations.PodamConstructor; import uk.co.jemos.podam.annotations.PodamStringValue; /** * A Country domain Model Object * * @author mtedone * */ public class Country implements Serializable { private static final long serialVersionUID = 1L; private final int countryId; private final String countryCode; private final String description; /** * Full constructor. * * @param countryId * The Country id * @param countryCode * The country code * @param description * The description */ @PodamConstructor(comment = "Immutable-like POJOs must be annotated with @PodamConstructor") public Country(int countryId, @PodamStringValue(length = 2) String countryCode, String description) { super(); this.countryId = countryId; this.countryCode = countryCode; this.description = description; } /** * @return the countryId */ public int getCountryId() { return countryId; } /** * @return the countryCode */ public String getCountryCode() { return countryCode; } /** * @return the description */ public String getDescription() { return description; } /** * Constructs a <code>String</code> with all attributes in name = value * format. * * @return a <code>String</code> representation of this object. */ @Override public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("Country ( ").append("countryId = ").append(countryId) .append(TAB).append("countryCode = ").append(countryCode) .append(TAB).append("description = ").append(description) .append(TAB).append(" )"); return retValue.toString(); } }
A print out from the running test shows that PODAM behaved correctly:
Country ( countryId = 1896117535 countryCode = � description = ½){‚áâ?� )
Similarly to Country, Article is an immutable-like POJO. Once defined, it will rarely change. In Article I wanted to show two additional features of PODAM:
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; import uk.co.jemos.podam.annotations.PodamConstructor; import uk.co.jemos.podam.annotations.PodamDoubleValue; import uk.co.jemos.podam.annotations.PodamIntValue; /** * @author mtedone * */ public class Article implements Serializable { private static final long serialVersionUID = 1L; private final int id; private final String description; private final Double itemCost; /** * Full constructor. * * @param id * The article id * @param description * The article description * @param itemCost * The item cost */ @PodamConstructor(comment = "Immutable-like POJOs must be annotated with @PodamConstructor") public Article(@PodamIntValue(maxValue = 100000) int id, String description, @PodamDoubleValue(minValue = 50.0) Double itemCost) { super(); this.id = id; this.description = description; this.itemCost = itemCost; } /** * @return the id */ public int getId() { return id; } /** * @return the description */ public String getDescription() { return description; } /** * @return the itemCost */ public Double getItemCost() { return itemCost; } /** * Constructs a <code>String</code> with all attributes in name = value * format. * * @return a <code>String</code> representation of this object. */ @Override public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("Article ( ").append("id = ").append(id).append(TAB) .append("description = ").append(description).append(TAB) .append("itemCost = ").append(itemCost).append(TAB) .append(" )"); return retValue.toString(); } } }
A print out of the POJO returned by Podam shows indeed that the tool behaved correctly once again.
Article ( id = 4210 description = ‹{€i¾«÷ñ itemCost = 50.41018423588162 )
The OrderItem Domain Object is simple in certain ways but interesting. The listing below shows some additional features of PODAM, namely: PODAM's ability to fill graphs of objects and the possibility to exclude one or more attributes from being processed by PODAM through the @PodamExclude annotation.
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; import uk.co.jemos.podam.common.PodamExclude; /** * @author mtedone * */ public class OrderItem implements Serializable { private static final long serialVersionUID = 1L; private int id; @PodamExclude(comment = "We don't want notes to be automatically filled") private String note; private double lineAmount; private Article article; /** * @return the id */ public int getId() { return id; } /** * @param id * the id to set */ public void setId(int id) { this.id = id; } /** * @return the note */ public String getNote() { return note; } /** * @param note * the note to set */ public void setNote(String note) { this.note = note; } /** * @return the lineAmount */ public double getLineAmount() { return lineAmount; } /** * @param lineAmount * the lineAmount to set */ public void setLineAmount(double lineAmount) { this.lineAmount = lineAmount; } /** * @return the article */ public Article getArticle() { return article; } /** * @param article * the article to set */ public void setArticle(Article article) { this.article = article; } /** * Constructs a <code>String</code> with all attributes in name = value * format. * * @return a <code>String</code> representation of this object. */ @Override public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("OrderItem ( ").append("id = ").append(id).append(TAB) .append("note = ").append(note).append(TAB) .append("lineAmount = ").append(lineAmount).append(TAB) .append("article = ").append(article).append(TAB).append(" )"); return retValue.toString(); } }
A print out of the test for OrderItem shows that PODAM behave correctly. One can indeed see that note was not filled and that the Article graphed object was automatically filled according to the PODAM semantics specified through annotations.
OrderItem ( id = 1109004359 note = null lineAmount = 0.5484388416699841 article = Article ( id = 12578 description = ‡Ò-ã6–r † itemCost = 50.718962180908136 ) )
Now to the juicy bit. If you look at the domain model above, you will see that the Order domain model contains a collection of OrderItems which in turn contain Articles.
A quick look at the Order implementation shows that PODAM can resolve both collections and object graphs very well. Also here I want to show an additional feature: the ability to specify the number of elements in a container-like structure (such as a Collection or a Map or an Array). The nbrElements attribute of the @PodamCollection annotation specifies how many elements are required.
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import uk.co.jemos.podam.annotations.PodamCollection; /** * @author mtedone * */ public class Order implements Serializable { private static final long serialVersionUID = 1L; private int id; private Calendar createDate; private double totalAmount; @PodamCollection(nbrElements = 5) private List<OrderItem> orderItems = new ArrayList<OrderItem>(); /** * @return the id */ public int getId() { return id; } /** * @param id * the id to set */ public void setId(int id) { this.id = id; } /** * @return the createDate */ public Calendar getCreateDate() { return createDate; } /** * @param createDate * the createDate to set */ public void setCreateDate(Calendar createDate) { this.createDate = createDate; } /** * @return the totalAmount */ public double getTotalAmount() { return totalAmount; } /** * @param totalAmount * the totalAmount to set */ public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; } /** * @return the orderItems */ public List<OrderItem> getOrderItems() { return orderItems; } /** * @param orderItems * the orderItems to set */ public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; } /** * Constructs a <code>String</code> with all attributes in name = value * format. * * @return a <code>String</code> representation of this object. */ @Override public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("Order ( ").append("id = ").append(id).append(TAB) .append("createDate = ").append(createDate.getTime()) .append(TAB).append("totalAmount = ").append(totalAmount) .append(TAB).append("orderItems = ").append(orderItems) .append(TAB).append(" )"); return retValue.toString(); } }
A quick print out of the test results show, once again, that PODAM handled the POJO structure correctly. Also note that PODAM filled the graphed objects according to their PODAM annotations.
Order ( id = 192600775 createDate = Mon Apr 25 12:19:28 BST 2011 totalAmount = 0.7796115521750312 orderItems = [OrderItem ( id = 1362702288 note = null lineAmount = 0.3104888223489538 article = Article ( id = 68362 description = àäÜ8’§¾cCô itemCost = 50.292714433943516 ) ), OrderItem ( id = 1735070766 note = null lineAmount = 0.8075636281304344 article = Article ( id = 94019 description = ˆCÝB4ÅH¿ itemCost = 50.24378266133199 ) ), OrderItem ( id = -1254701210 note = null lineAmount = 0.4776720511109557 article = Article ( id = 67989 description = tMjÍœ`‹Ó itemCost = 50.72391622167959 ) ), OrderItem ( id = 970589631 note = null lineAmount = 0.33309454079741674 article = Article ( id = 86379 description = Zõºù�pA�³ itemCost = 50.61826823373968 ) ), OrderItem ( id = -1809775451 note = null lineAmount = 0.714713231956349 article = Article ( id = 21999 description = Úh’è@¯’}Ú itemCost = 50.323456491701855 ) )] )
Nothing special about the Address domain model.
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; /** * @author mtedone * */ public class Address implements Serializable { private static final long serialVersionUID = 1L; private String address1; private String address2; private String city; private String zipCode; private Country country; /** * @return the address1 */ public String getAddress1() { return address1; } /** * @param address1 * the address1 to set */ public void setAddress1(String address1) { this.address1 = address1; } /** * @return the address2 */ public String getAddress2() { return address2; } /** * @param address2 * the address2 to set */ public void setAddress2(String address2) { this.address2 = address2; } /** * @return the city */ public String getCity() { return city; } /** * @param city * the city to set */ public void setCity(String city) { this.city = city; } /** * @return the zipCode */ public String getZipCode() { return zipCode; } /** * @param zipCode * the zipCode to set */ public void setZipCode(String zipCode) { this.zipCode = zipCode; } /** * @return the country */ public Country getCountry() { return country; } /** * @param country * the country to set */ public void setCountry(Country country) { this.country = country; } /** * Constructs a <code>String</code> with all attributes * in name = value format. * * @return a <code>String</code> representation * of this object. */ public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("Address ( ") .append("address1 = ").append(this.address1).append(TAB) .append("address2 = ").append(this.address2).append(TAB) .append("city = ").append(this.city).append(TAB) .append("zipCode = ").append(this.zipCode).append(TAB) .append("country = ").append(this.country).append(TAB) .append(" )"); return retValue.toString(); } }
A quick print out of the unit test shows that Address has been correctly filled:
Address ( address1 = ½í9‰ú¥aŸq address2 = ‰u÷BÐ' õ8H city = ?¹ä5o"5â8 zipCode = ’?ëÈg(®–2Ó country = Country ( countryId = 1202210523 countryCode = µ description = NvV\<ºå¿h ) )
The BankAccount Domain Model Object is simple and it does not present anything interesting. One could have limited the length of the sort code to eight characters.
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; /** * @author mtedone * */ public class BankAccount implements Serializable { private static final long serialVersionUID = 1L; private int account; private String bank; private String sortCode; private double balance; /** * @return the account */ public int getAccount() { return account; } /** * @param account * the account to set */ public void setAccount(int account) { this.account = account; } /** * @return the bank */ public String getBank() { return bank; } /** * @param bank * the bank to set */ public void setBank(String bank) { this.bank = bank; } /** * @return the sortCode */ public String getSortCode() { return sortCode; } /** * @param sortCode * the sortCode to set */ public void setSortCode(String sortCode) { this.sortCode = sortCode; } /** * @return the balance */ public double getBalance() { return balance; } /** * @param balance * the balance to set */ public void setBalance(double balance) { this.balance = balance; } /** * Constructs a <code>String</code> with all attributes * in name = value format. * * @return a <code>String</code> representation * of this object. */ public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("BankAccount ( ") .append("account = ").append(this.account).append(TAB) .append("bank = ").append(this.bank).append(TAB) .append("sortCode = ").append(this.sortCode).append(TAB) .append("balance = ").append(this.balance).append(TAB) .append(" )"); return retValue.toString(); } }
A quick print out of the unit test result shows that the object is filled as expected.
BankAccount ( account = 1363455548 bank = @ÄŸ[´MÝgŸ® sortCode = u€·È´Çˆ× balance = 0.06934020055908185 )
The Client Domain Model Object is the root of the graph and indeed the object which puts it all together. Not much to say here in terms of customisation of PODAM behaviour. We wanted the first name to be Michael, three Orders, two addresses and the default number of collection elements for bank accounts (which at the time of writing is 1).
/** * */ package uk.co.jemos.podam.test.dto.docs.example; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import uk.co.jemos.podam.annotations.PodamCollection; import uk.co.jemos.podam.annotations.PodamStringValue; /** * @author mtedone * */ public class Client implements Serializable { private static final long serialVersionUID = 1L; @PodamStringValue(strValue = "Michael") private String firstName; private String lastName; private Calendar dateCreated; // Let's make some orders @PodamCollection(nbrElements = 3) private List<Order> orders = new ArrayList<Order>(); // Let's have few addresses @PodamCollection(nbrElements = 2) private List<Address> addresses = new ArrayList<Address>(); // Default is one bank account private List<BankAccount> bankAccounts = new ArrayList<BankAccount>(); /** * @return the firstName */ public String getFirstName() { return firstName; } /** * @param firstName * the firstName to set */ public void setFirstName(String firstName) { this.firstName = firstName; } /** * @return the lastName */ public String getLastName() { return lastName; } /** * @param lastName * the lastName to set */ public void setLastName(String lastName) { this.lastName = lastName; } /** * @return the dateCreated */ public Calendar getDateCreated() { return dateCreated; } /** * @param dateCreated * the dateCreated to set */ public void setDateCreated(Calendar dateCreated) { this.dateCreated = dateCreated; } /** * @return the orders */ public List<Order> getOrders() { return orders; } /** * @param orders * the orders to set */ public void setOrders(List<Order> orders) { this.orders = orders; } /** * @return the addresses */ public List<Address> getAddresses() { return addresses; } /** * @param addresses * the addresses to set */ public void setAddresses(List<Address> addresses) { this.addresses = addresses; } /** * @return the bankAccounts */ public List<BankAccount> getBankAccounts() { return bankAccounts; } /** * @param bankAccounts * the bankAccounts to set */ public void setBankAccounts(List<BankAccount> bankAccounts) { this.bankAccounts = bankAccounts; } /** * Constructs a <code>String</code> with all attributes * in name = value format. * * @return a <code>String</code> representation * of this object. */ public String toString() { final String TAB = " "; StringBuilder retValue = new StringBuilder(); retValue.append("Client ( ") .append("firstName = ").append(this.firstName).append(TAB) .append("lastName = ").append(this.lastName).append(TAB) .append("dateCreated = ").append(this.dateCreated).append(TAB) .append("orders = ").append(this.orders).append(TAB) .append("addresses = ").append(this.addresses).append(TAB) .append("bankAccounts = ").append(this.bankAccounts).append(TAB) .append(" )"); return retValue.toString(); } }
A quick print out of the unit test shows that PODAM has filled the whole graph correctly and according to custom annotations.
Client ( firstName = Michael lastName = 26DUfeITA6 dateCreated = Sun Apr 12 22:05:29 BST 2015 orders = [Order ( id = 1821274116 createDate = Sun Apr 12 22:05:29 BST 2015 totalAmount = 0.8772870622958445 orderItems = [ OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) )] ), Order ( id = 1821274116 createDate = Sun Apr 12 22:05:29 BST 2015 totalAmount = 0.8772870622958445 orderItems = [ OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) )] ), Order ( id = 1821274116 createDate = Sun Apr 12 22:05:29 BST 2015 totalAmount = 0.8772870622958445 orderItems = [ OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) ), OrderItem ( id = -1673385160 note = null lineAmount = 0.8522344642870483 article = Article ( id = 29115 description = 2r6esZswGm itemCost = 50.0 ) )] )] addresses = [ Address ( address1 = 5AYKjJGUmu address2 = gOFji9ODoq city = 1lVQDz_Plr zipCode = kumAYhYhfY country = Country ( countryId = -1776122448 countryCode = aL description = ZE41OjRbAq ) ), Address ( address1 = 5AYKjJGUmu address2 = gOFji9ODoq city = 1lVQDz_Plr zipCode = kumAYhYhfY country = Country ( countryId = -1776122448 countryCode = aL description = ZE41OjRbAq ) )] bankAccounts = [ BankAccount ( account = -406255777 bank = z2yKqcpL5e sortCode = 6RMFOq9kim balance = 0.25778714401085623 ), BankAccount ( account = -406255777 bank = z2yKqcpL5e sortCode = 6RMFOq9kim balance = 0.25778714401085623 ), BankAccount ( account = -406255777 bank = z2yKqcpL5e sortCode = 6RMFOq9kim balance = 0.25778714401085623 ), BankAccount ( account = -406255777 bank = z2yKqcpL5e sortCode = 6RMFOq9kim balance = 0.25778714401085623 ), BankAccount ( account = -406255777 bank = z2yKqcpL5e sortCode = 6RMFOq9kim balance = 0.25778714401085623 )] )