Now Configuration Utils starts to become more serious. Take a moment and consider the following?
How many times have you needed to persist a Java Bean between sessions, or JVM runs?
How many times have you created a configuration of some sort, created a user form to fill it up by the user, used a Bean to do so and manage the data retrieved and then... have to convert it to a persistence layer back and forth?
How many times have you managed configurations data compiled of more than string values, and had to create boilerplate code to convert it back and forth from the persistence layer and your classes?
We're sure that most of you would answer "plenty of times" to the above questions and other similar ones that Configuration Utils tries to solve.
Since the specification of Java Beans by SUN in the 90s, that the Java community has adopted Java Beans like the standard way of representing data. Several developments have been built upon this specification, like EJBs, and several APIs that rely on this convention to access objects and the contained data.
So this is something we all accept and consider normal, and use extensivly. So why do we keep managing configurations like key/value pairs? Why do we keep creating boilerplate code for all these, conversion and type-check operations, reading and writing to disk or other persistence layer, etc.?
Well, maybe we need something like Configuration Utils, to help us loose this extra weight in our development efforts. Lets see how it handles Java Beans directly.
Let's take the following Java Bean.
public class MockupPojo { /** Name */ private String name; /** Address */ private String address; /** Age */ private int age; /** Birth */ private Date birthDate; /** * @return the address */ public String getAddress() { return address; } /** * @param address the address to set */ public void setAddress(String address) { this.address = address; } /** * @return the age */ public int getAge() { return age; } /** * @param age the age to set */ public void setAge(int age) { this.age = age; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the birthDate */ public Date getBirthDate() { return birthDate; } /** * @param birthDate the birthDate to set */ public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } }
Nothing fancy about this. A simple example with 4 attributes of three types: String, int and Date. And the conventioned getters and setters, that any IDE will automatically generate for you, like these were.
Now, if we wanted to save these to, say, a properties file, we had to:
When we wished to read them back, the inverse process would have to take place, placing extra care on step one, since converting to String is always possible, but from String may result in invalid values if the data is corrupted.
We will not bore you with a sample code since it's not pretty and completely unnecessary like we will see.
Now, how could Configuration Utils help us?
Check out the actual code below of a real use of the previous bean:
1 IConfigurations configAPI = new ConfigurationsPreferencesImpl(); 2 3 MockupPojo bean = new MockupPojo(); 4 bean.setName("Abraham Lincoln"); 5 bean.setAddress("Washington DC - President's Street 1"); 6 bean.setAge(300); 7 bean.setBirthDate(new GregorianCalendar(2007, 10, 2).getTime()); 8 9 configAPI.writeConfiguration("TestConfigurations", "bean", bean)); 10 11 bean = config.readConfiguration("TestConfigurations", "bean", MockupPojo.class);
See how easy life can be? :-D
Let's take a look in the backstage to see what has happened.
When writeConfiguration receives an object, it parses it's class in search of the conventioned attributes with a getter and setter. These must exist and comply with the convention of Java Beans, like setters with one parameter for instance, else an error will be issued and the corresponding attribute will be ignored.
The process analyzes the data type for each attribute and tries to use the attribute. This may mean converting a primitive type to the corresponding Class implementation (int to Int, long to Long, etc), or checking to see if the current type has a constructor that receives a String single argument. This is the convention imposed by Configuration Utils.
Note: For an object to be supported by the API for saving/writing it must implement the toString method and have a String single argument constructor. The API will use the toString to save the value and construct an object with the loaded values by calling the String constructor. This works for most Java classes that already exist and is a simple way to add support for your own custom classes if needed.
In this process the keys are also infered, with the names of the attributes. Or to be more precise, taken from the getter name. "getName" will implicate a "name" key. Again, the convention is respected.
Note: Both the conventioned "getAttribute" and "isAttribute" (for boolean attributes) are supported.
After this, a key/value pairs object (a Properties object) is created with the inferred keys and the converted attribute values converted to Strings, and saved to the persistence layer with the passed configuration/section ID. This is exactly the same as already explained in the Usage Section. In fact internally the same method is called after the conversion has taken place and a key/value Properties object has been created.
Here we need to pass the class of the object. This will instruct the readConfiguration method to know how to read the data. It will again parse this class in search of the conventioned getters and so on.
The read method will internally call the readConfiguration that returns the key/value Properties pairs, and then create an instance of the provided class and set all fields that have a value in the repository. In this fase the delicate process of converting the values from String to the specified Object type, using the String constructors, takes place.
The API has built in detailed exception handling functionalities. It will try to recover from all errors possible, logging all informations about the problems it encountered. When this is not possible the called methods will return false or null values indicating that the operation did not succeed. All this is well documented in the JavaDocs API.
Let's quickly enumerate the main advantages that we have covered in the examples above:
Much convention leeds to little flexibility.
One can't have different keys and attribute names.
One can't have a choice over witch attributes to save and not to.
One can't have default values assigned to attributes not persisted to the repository before...
...Or can we? And the answer is, yes we can!
Check out the Using Configuration Utils Annotations to extend the functionalities covered so far.