Customizable Java modules

Posted by | No Tags | Software developement | No Comments on Customizable Java modules

In the course of developing applications to fulfil business needs, occasionally we have to fulfil similar demands over and over again. At such occasions –as nobody likes to repeat themselves –the need arises to solve these situations in a reusable modular way. However, these demands are only similar, not identical, which leads to questions and problems. I will proceed to present a method, which can solve a number of such issues and through which customizable, expandable modules can be created in Java language.

What is it all about?

To make it easier to understand what I mean, let’s take user management as an example. We generously disregard the fact that several ready-made solutions exist, and for the sake of the example, say that we would like to create it ourselves. Let’s exclude authentication as well, and focus only on the fact that the users have data that we want to manage. Besides, let’s also suppose that there will be other modules, functions that depend on our user object, thus a general solution is needed.
Our aim is a solution that can quickly be brought into our projects and can easily be modified if necessary. Extra points will be awarded if the incidental future corrections will only need to be made in one common area and it (can) function in every project using the module.

How can it be achieved?

The most comfortable solution would be to actually program the module and every functionality, including the database layer with entities, and then place an UI over it and use this complete module everywhere. However, this is not viable as once we have written the entities, they cannot be modified from the project that uses them. If we haven’t included e.g. a telephone number field in the User entity, then there is no way to include it. True? False!
The EclipseLink JPA implementation provides us with a very effective tool: the @VirtualAccessMethods annotation. With its help we can indicate that our entity can be expanded and it provides opportunity for defining virtual properties without modifying the source file or redeploying the persistence unit.

Take the following class as an example:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

In addition to the annotation we have to provide a method for the JPA, through which it can store the values belonging to the virtual properties in the memory. For this a getter and setter method have to be provided (their name can be overridden in the annotation), and have to place a data structure behind it. A map for example can be a good choice, naturally in a transient way, as we do not want to persist the map itself. The complemented class is the following:

@Entity
@VirtualAccessMethods
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Transient
    private Map<String, Object> extensions = new HashMap<>();

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public <T> T get(String name) {
        return (T) extensions.get(name);
    }

    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }

}

This way we have prepared our entity to be able to manage virtual fields, that is, fields that were not recorded in the class in advance. The next step is to be able to define what these extra fields are. This is done in the eclipselink-orm.xml file: here we have the opportunity to give the data defining the fields. The place of the file is in META-INF, next to persistence.xml. Its name can be overridden in the persistence.xml. As the persistence.xml is already located in the project which uses this module, the addition of the extra fields can be done in this project as well. If, for example we wish to expand the user with telephone number and date of birth, then the eclipse-link-orm.xml can appear as follows:

<?xml version="1.0"?>
<entity-mappings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/orm http://www.eclipse.org/eclipselink/xsds/eclipselink_orm_2_1.xsd"
    version="2.1">

    <entity class="com.wcs.jeewcslib.idm.api.entity.User">
        <attributes>
            <basic name="phone"
                   access="VIRTUAL"
                   attribute-type="String">
                <column name="PHONE"/>
            </basic>
            <basic name="birthdate"
                   access="VIRTUAL"
                   attribute-type="java.util.Date">
                <temporal>TIMESTAMP</temporal>
                <column name="BIRTHDATE" />
            </basic>
        </attributes>
    </entity>
</entity-mappings>

As it can be seen, we provide the entity we would like to expand in the xml, and then list the extra properties. We have to set the type of mapping (in this case it is basic in both cases) by which we define the name and the type of the property and we also indicate that it is a virtual property. In addition, we have to give the name of the column to which we would like to map it in the database. We can also use <temporal>TIMESTAMP</temporal> tag with the date of birth: this corresponds to the @Temporal(TemporalType.TIMESTAMP) annotation. As this example shows, the equivalent of the settings that can be given by annotation are also present in the xml method of provision, that is, we can define everything with this method, just as if we were doing it with annotations, declaring it in the class source. Further information on eclipselink-orm.xml can be read here, and more information on the complete method is available here.

Having completed the above steps, we have nothing else to do than to include the necessary columns in the database and our system will be able to handle them. Liquibase is a tool that is excellent for database version control, for which a Netbeans plugin is available by the name LiquiFace.

What about the user interface?

As I have mentioned above, our aim would be to create an out-of-the-box, solution that is customizable at the same time. The entity is expandable, so customizability is solved, but it is not perfect as there is no user interface for the management of the data. Basically, the question arises whether it is necessary to make it reusable, as the display layer can be completely different in each project, thus there might not be a point in preparing a general interface. However, in case we use the same technology in several projects, it is very useful to have a ready-made solution.

In such cases automatic form generation can be very useful. I have already written an entry on the operation of Metawidget so I will not present it in detail here. In short, the principle is that the tool generates a form for our entity on the basis of the available meta data (Java classes, annotations, etc.), checking each property individually, and deciding what component to create for them.

In practise we solved the problem by preparing a wrapper class for the entity to be expanded in the user project, which in turn will implement a uniform interface, indicating which entity it expands. For the above example, we would prepare the following class:

public class UserView implements EntityView<User> {

    private User user;

    @Override
    public void wrap(User user) {
        this.user = user;
    }

    public Long getId() {
        return user.getId();
    }

    public void setId(Long id) {
        user.setId(id);
    }

    public String getName() {
        return user.getName();
    }

    public void setName(String name) {
        user.setName(name);
    }

    public String getPhone() {
        return user.get("phone");
    }

    public void setPhone(String phone) {
        user.set("phone", phone);
    }

    public String getBirthdate() {
        return user.get("birthdate");
    }

    public void setBirthdate(Date birthdate) {
        user.set("birthdate", birthdate);
    }

}

The EntityView interface looks like this:

public interface EntityView<T extends Object> {
    void wrap(T t);
}

The wrapper class has two functions: firstly, it can be used in the user project to typically achieve the extra properties, and also to provide it to the preliminarily prepared user interface which will be able to include the extra data in the display. In detail: our interface should consist of a list of users, where, if we chose a user, their data can be edited on a form. In addition, we can create new users and delete existing ones.

In this case, the list and the function buttons can completely be prepared in advance, and only the form has to contain individual information in each case. The implemented interface plays a role here: as the logic underlying the interface knows that the provided class is and EntityView, following the selection of a user it can instantiate the class and hand the user over to it with the wrap method. From the wrapped user copy we can generate the appropriate form with the help of Metawidget. If we can perform this, the concept can be improved, and with the help of the Metawidget and a bit of our own development thrown in, we can achieve that the extra fields will appear in the columns of the list, thus we can really provide an out-of-the-box solution.
In the two pictures below a generated list and a generated form can be seen, the telephone field is virtual property in both cases.

Generált lista virtuális telefon oszloppal

Generált lista virtuális telefon oszloppal

Generált űrlap virtuális telefon mezővel

Generated form with virtual phone field

Conclusion

Our module created with the above method can nicely be wrapped in our own jar, and can thereafter be easily used in any project. This solution has the advantage that in case corrections or new developments are needed later, all user projects can be updated easily through updating the jar. To customize it, we only have to create a wrapper class and include the missing fields in the eclipselink-orm.xml. We have thus achieved our goal: the reusable, customizable module is ready, and what’s more, it is easy to manage!


No Comments

Leave a comment