In this page we will go through a complete 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.annotations.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 = á‰ÞÎDBîñ³q dateCreated = Mon Apr 25 12:58:15 BST 2011 orders = [Order ( id = -1214029713 createDate = Mon Apr 25 12:58:15 BST 2011 totalAmount = 0.15824563659082125 orderItems = [OrderItem ( id = 437697937 note = null lineAmount = 0.5912186097270055 article = Article ( id = 78249 description = å‰@½7¬8éñ itemCost = 50.725438562464575 ) ), OrderItem ( id = 1001796352 note = null lineAmount = 0.10393017626286882 article = Article ( id = 10520 description = í¬ç¨ò¿Îå itemCost = 50.360370831675326 ) ), OrderItem ( id = 599405735 note = null lineAmount = 0.0404010474189852 article = Article ( id = 57216 description = {RóQñOè… itemCost = 50.128059604375046 ) ), OrderItem ( id = 117731007 note = null lineAmount = 0.9593616974720982 article = Article ( id = 61107 description = £5°‘èQõJ÷ itemCost = 50.013514672179845 ) ), OrderItem ( id = -1073324010 note = null lineAmount = 0.7772903427345885 article = Article ( id = 78961 description = Ë’áÂ2Ò%À÷ itemCost = 50.22020062244809 ) )] ), Order ( id = -1651635160 createDate = Mon Apr 25 12:58:15 BST 2011 totalAmount = 0.6645246082071045 orderItems = [ OrderItem ( id = 109866940 note = null lineAmount = 0.8745830931110706 article = Article ( id = 71651 description = ôû„âÛ?Ï4K itemCost = 50.381801769956546 ) ), OrderItem ( id = 1095604915 note = null lineAmount = 0.9123939846823909 article = Article ( id = 17489 description = í8CðM…¸¿â itemCost = 50.497844673239534 ) ), OrderItem ( id = -1798096015 note = null lineAmount = 0.4721243067669355 article = Article ( id = 30372 description = º£©6,†]'] itemCost = 50.28528682613012 ) ), OrderItem ( id = 2037554497 note = null lineAmount = 0.8879105975840326 article = Article ( id = 79913 description = ò¿x{f?>^m itemCost = 50.24873540554301 ) ), OrderItem ( id = 1441773834 note = null lineAmount = 0.46836761974290253 article = Article ( id = 23240 description = <“[¦¶ÕŽ9ñà itemCost = 50.818364354492246 ) )] ), Order ( id = -1124161683 createDate = Mon Apr 25 12:58:15 BST 2011 totalAmount = 0.8423129969362227 orderItems = [ OrderItem ( id = 11819221 note = null lineAmount = 0.2078458049765809 article = Article ( id = 25352 description = Ÿ“µØ;AŒXþ itemCost = 50.28236458449263 ) ), OrderItem ( id = -1587301956 note = null lineAmount = 0.015293480987208175 article = Article ( id = 19402 description = ö€ÙÑŠ´’*‰§ itemCost = 50.923520177819256 ) ), OrderItem ( id = 2018175196 note = null lineAmount = 0.3628655588134182 article = Article ( id = 13429 description = çeЉ¬ÔÌëõˆ itemCost = 50.059012227627704 ) ), OrderItem ( id = 20895848 note = null lineAmount = 0.30105000549975536 article = Article ( id = 4134 description = ’Ôˆ(„* addresses = [ Address ( address1 = ûÏ>H„•ôݲ address2 = ôàG¸}ÚÜF¤7 city = ©ÜH·¥ÖÏF zipCode = ;p¨˜yíHÉ country = Country ( countryId = 1721185184 countryCode = ?t description = üÙ“R"€~ÛA ) ), Address ( address1 = Õºgß?oæz2 address2 = Bª¢ÛšâÃÂ”× city = UéO?„†>ÛVû zipCode = Q“b8S÷â¼= country = Country ( countryId = -866208309 countryCode = yg description = a "{tí‡lC ) )] bankAccounts = [BankAccount ( account = -1814262486 bank = ?”“]SÏÑïoR sortCode = :¶QÒ9Y8§ ® balance = 0.132176225216333 )] ) +-----------------------------------------------------