Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

2015 Proof Of Concept: JSF 2.0 Distributed Multitiered Application

DZone's Guide to

2015 Proof Of Concept: JSF 2.0 Distributed Multitiered Application

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

This article aims to present a different approach on handling JSF distributed multitiered applications. Try the suggested steps in order to build robust distributed applications, that serve responses instantly, no matter the complexity of the client request.

The components of this article are:

1. Identifying general requirements of Java distributed multitiered applications

2. Java architectural solutions in order to satisfy requirements : classic solutions vs proposed proof of concept

3. JSF2.0 Distributed Multitiered Application Proof of Concept (implementation and advantages)

Development tools needed:

  • an IDE ( I used NetBeans)
  • Java SE 1.6 or higher
  • Maven 2.0 or higher
  • for testing your code locally, you should have an application server installed. I used Oracle Glassfish 4.0  , but you are free to choose any other server.
  • as RDBMS I used Oracle 11g Enterprise Edition ( I made use of two different database instances installed locally)

1. Identifying general requirements of Java distributed multitiered applications

You can think about distributed applications in terms of breaking down an application into individual computing systems that can be spread across a network of computers, but  they still work together to do cooperative tasks. Below, I listed a few motivations for distributing an application this way are many:

  • Solving larger problems without resorting to larger computer systems can be done by computing tasks in parallel by breaking a process into smaller pieces. Instead of complex hardware systems, you can use smaller, cheaper, easier-to-find hardware components.
  • Large sets of data are difficult to relocate, but they are easier to control and administrated where they are stored. Users rely on remote data servers to provide the informations they need.
  • In  case of needing failure handling mechanisms, systems can use redundant processing  application components on multiple networked computers. In this manner, if a machine or agent process goes down, the task that the machine was executing can still carry on.

Developing java distributed applications requires the following:

  • partitioning and distributing data and functions
  • flexible, extendible communication protocols
  • multithreading
  • security mechanisms

2. Java architectural solutions in order to satisfy requirements : classic solution vs proposed proof of concept

The Java EE platform uses a distributed multitiered application model for enterprise applications. Application logistics are divided into components according to function, and the application components that makeup a Java EE application are installed on various machines, depending on the tier in the multitiered Java EE environment to which the application component belongs.

A classic Java architecture solution for an distributed multitiered application is similar to the following diagram:


  •  Client-tier components that run on the client machine. An application client runs on a client machine and provides a way for users to handle tasks that require a richer user interface than can be provided by a markup language.
  •  Web-tier components that run on the Java EE server. Java EE web components are either servlets or webpages created using Java Server Faces technology and/or JSP technology(JSP pages).
  • Business-tier components that run on the Java EE server. Business code, which is logic that solves or meets the needs of a particular business domain such as banking, retail, or finance, is handled by enterprise beans running in either the business tier or the web tier
  • Enterprise information system(EIS)-tier software that runs on the EIS server. The enterprise information system tier handles EIS software and includes enterprise infrastructure systems, such as enterprise resource planning(ERP), mainframe transaction processing, database systems, and other legacy information systems . For example, Java EE application components might need access to enterprise information systems for database connectivity.

The previous architectural solution states that for each database that the application will operate with, will exist associated sets of enterprise bean(s) that will manipulate transactions for that database. So, (duplicate) java enterprise beans implementations will be needed for each of the database. Instead of using java enterprise beans for each of the database operations, the architecture proposed within this article proof of concept application has the following overview:


As previous discussed, this implementation also has:

  • Client-tier components that run on the client machine.
  • Web-tier components (java server faces) that run on the Java EE server.
  • Business-tier components (Java Persistence Entities , Session Beans, Message-Driven Beans ) that run on the Java EE server.
  • Enterprise information system(EIS)-tier software that runs on the EIS server

But in this case, the enterprise beans will use one database connection and for communicating with the other database, a database link will be used for querying it.

3. JSF2.0 Distributed Multitiered Application Proof of Concept (implementation and advantages)

Let's imagine the following scenario: we have an online store application; the application has tables related to activity from Romania on the database SHOP_RO, and the activity from Belgium is stored on SHOP_BE. The common activity is stored in the database SHOP_RO (tables like PRODUCTS, PAYMENT, ADDRESSES are common).


In order to ease the communication the databases SHOP_BE and SHOP_RO databases we will use private links. A database link is a schema object in one database that enables you to access objects on another database (in our case the other database is also an Oracle Database system). A database link is unidirectional and facilitates communication between database systems; the database links can be used to send data between applications.  

Before executing the sql commands for creating database links, we should create the necessary database services on each our databases. For example, the service db_shop_ro will created using the Net Configuration Assistant application.


Click on Next (Continuare).


Continue to click Next(Continuare) and test the service using a database user. In my case, the user AMMBRA exists on both databases and I will use it to test the service.


If the each of the service test was successful, then we could go on with executing the database links on each of the databases (remember, two databases can communicate using two services, that exist on each of the databases).

--execute this on database SHOP_BE  
CREATE PUBLIC DATABASE LINK "SHOP_RO"
  CONNECT TO "AMMBRA" IDENTIFIED BY 'Ammbra2013'
  Using 'db_shop_ro';

 --for testing 
   SELECT USER FROM DUAL@shop_ro;



--execute this on database SHOP_RO  
  CREATE PUBLIC DATABASE LINK "SHOP_BE"
   CONNECT TO "AMMBRA" IDENTIFIED BY 'Ammbra2013'
   USING 'db_shop_be';
--for testing purpose 
   SELECT USER FROM DUAL@shop_be;

The database SHOP_RO will serve as persistence source for our JSF based application. Our application layers will be as following:


Let's examine how can the application function with the two given databases; for login, we will need to validate the user details against the two tables USER_ACCOUNT_BE and USER_ACCOUNT_RO, that exist on the associated databases. In order to assure transparency of the data for the users, we will create the following view (that offers an overall transparency of the localized data) :

CREATE OR REPLACE VIEW AMMBRA.User_Account_All
AS
SELECT * FROM Ammbra.User_Account_Ro
UNION ALL
SELECT * FROM Ammbra.User_Account_Be@shop_be;

When a user will login in our application, the entity UserAccountAll will offer access to the view USER_ACCOUNT_ALL previously created. 

@Entity
@Table(name = "USER_ACCOUNT_ALL", catalog = "", schema = "AMMBRA")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "UserAccountAll.findAll", query = "SELECT c FROM UserAccountAll c"),
    @NamedQuery(name = "UserAccountAll.findByAlias", query = "SELECT c FROM UserAccountAll c WHERE c.alias = :alias"),
    @NamedQuery(name = "UserAccountAll.findByPassword", query = "SELECT c FROM UserAccountAll c WHERE c.password = :password"),
    @NamedQuery(name = "UserAccountAll.findByName", query = "SELECT c FROM UserAccountAll c WHERE c.NAME = :NAME"),
    @NamedQuery(name = "UserAccountAll.findBySurname", query = "SELECT c FROM UserAccountAll c WHERE c.surname = :surname"),
    @NamedQuery(name = "UserAccountAll.findByDateOfBirth", query = "SELECT c FROM UserAccountAll c WHERE c.dateOfBirth = :dateOfBirth"),
    @NamedQuery(name = "UserAccountAll.findByCnp", query = "SELECT c FROM UserAccountAll c WHERE c.cnp = :cnp"),
    @NamedQuery(name = "UserAccountAll.findByEmail", query = "SELECT c FROM UserAccountAll c WHERE c.email = :email"),
    @NamedQuery(name = "UserAccountAll.findByPhone", query = "SELECT c FROM UserAccountAll c WHERE c.phone = :phone"),
    @NamedQuery(name = "UserAccountAll.findByCountry", query = "SELECT c FROM UserAccountAll c WHERE c.country = :country")})
public class UserAccountAll implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Size(max = 25)
    @Column(name = "ALIAS", length = 25)
    private String alias;
    @Size(max = 25)
    @Column(name = "PASSWORD", length = 25)
    private String password;
    @Size(max = 100)
    @Column(name = "NAME", length = 100)
    private String name;
    @Size(max = 100)
    @Column(name = "SURNAME", length = 100)
    private String surname;
    @Column(name = "DATE_OF_BIRTH")
    @Temporal(TemporalType.TIMESTAMP)
    private Date dateOfBirth;
    @Size(max = 13)
    @Column(name = "CNP", length = 13)
    private String cnp;
    // @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")//if the field contains email address consider using this annotation to enforce field validation
    @Size(max = 100)
    @Column(name = "EMAIL", length = 100)
    private String email;
    @Size(max = 30)
    @Column(name = "PHONE", length = 30)
    private String phone;
    @Size(max = 25)
    @Column(name = "COUNTRY", length = 25)
    private String country;

    public UserAccountAll() {
    }

    public UserAccountAll(String alias) {
        this.alias = alias;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

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

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public String getCnp() {
        return cnp;
    }

    public void setCnp(String cnp) {
        this.cnp = cnp;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (alias != null ? alias.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof UserAccountAll)) {
            return false;
        }
        UserAccountAll other = (UserAccountAll) object;
        if ((this.alias == null && other.alias != null) || (this.alias != null && !this.alias.equals(other.alias))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.tutorial.ammbrashop.UserAccountAll[ alias=" + alias + " ]";
    }
    
}

The entity will be manipulated by the UserAccountAllFacade class

@Stateless
public class UserAccountAllFacade extends AbstractFacade< UserAccountAll> {
    @PersistenceContext(unitName = "com.tutorial_AmmbraShop_war_1.0-SNAPSHOTPU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public UserAccountAllFacade() {
        super(UserAccountAll.class);
    }
    
}

The above class extends the behavior from AbstractFacade class:

public abstract class AbstractFacade<T> {

    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
    }

    public void edit(T entity) {
        getEntityManager().merge(entity);
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    @SuppressWarnings("unchecked")
    public T findOne(String query, Object... params) {
        return (T) getNamedQueryWith(query, params).getSingleResult();
    }

    public List<T> findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(int[] range) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0] + 1);
        q.setFirstResult(range[0]);
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

}

The UserAccountAllFacade class is injected inside LoginController class:

@ManagedBean(name="loginController")
@SessionScoped
public class LoginController implements Serializable {

    private static final long serialVersionUID = 7765876811740798583L;
    @EJB
    private com.tutorial.ammbrashop.login.control.UserAccountAllFacade ejbFacade;

    private String alias;
    private String password;
    private UserAccountAll user;
    
    private boolean loggedIn;


    /**
     * Login operation.
     *
     * @return
     */
    public String doLogin() {
        
        user = ejbFacade.find(alias);
        // Successful login
        if (user != null && user.getPassword().equals(password)) {
            loggedIn = true;
            return "index";
        } else if (user != null && !user.getPassword().equals(password)) {
            // Set login ERROR
            FacesMessage msg = new FacesMessage("password is invalid. login not allowed", "ERROR MSG");
            msg.setSeverity(FacesMessage.SEVERITY_ERROR);
            FacesContext.getCurrentInstance().addMessage(null, msg);
        } else {
            // Set login ERROR
            FacesMessage msg = new FacesMessage("Invalid alias! ", "ERROR MSG");
            msg.setSeverity(FacesMessage.SEVERITY_ERROR);
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }
        // To to login page
        return "login";

    }


    // ------------------------------
    // Getters & Setters 
    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String Password) {
        this.password = password;
    }
    public UserAccountAll getUser() {
        return user;
    }

    public void setUser(UserAccountAll user) {
        this.user = user;
    }
    public boolean isLoggedIn() {
        return loggedIn;
    }

    public void setLoggedIn(boolean loggedIn) {
        this.loggedIn = loggedIn;
    }


}

How will be handled insert/update/delete operations?

When a insert/update/delete operation should be performed we cannot use a view. Let's inspect the following scenario: we should insert a new user having its account country set to Belgium. In order to insert it into SHOP_BE.USER_ACCOUNT_BE table we should execute the query :

Insert into USER_ACCOUNT_BE@SHOP_BE (ALIAS,PASSWORD,NAME,SURNAME,DATE_OF_BIRTH,CNP,EMAIL,PHONE, COUNTRY) values ('anamih','mar10mih','Mihalceanu','Ana-Maria',to_date('15-AUG-86','DD-MON-RR'),'28608156889432','mihalcana@gmail.com','0744666311', 'Belgium'); 		

The java code of the class UserAccountAllFacade should be modified by overriding create method:

@Override
    public void create(UserAccountAll entity) {
        //super.create(entity); 

        if (entity.getCountry().equals("Romania")) {
            Query q = em.createNativeQuery("Insert into USER_ACCOUNT_RO (ALIAS,PASSWORD,NAME,SURNAME,DATE_OF_BIRTH,CNP,EMAIL,PHONE, COUNTRY) values ("
                    + entity.getAlias() + ",'" + entity.getPassword() + "','" + entity.getName() + "','" + entity.getSurname() + "','" + entity.getDateOfBirth() + "','" + entity.getCnp() + "','" + entity.getEmail()+ "','" + entity.getPhone()+ "','" + entity.getCountry() + "')");
            q.executeUpdate();
        } else {
            Query q = em.createNativeQuery("Insert into USER_ACCOUNT_BE@SHOP_BE (ALIAS,PASSWORD,NAME,SURNAME,DATE_OF_BIRTH,CNP,EMAIL,PHONE, COUNTRY) values ("
                    + entity.getAlias() + ",'" + entity.getPassword() + "','" + entity.getName() + "','" + entity.getSurname() + "','" + entity.getDateOfBirth() + "','" + entity.getCnp() + "','" + entity.getEmail()+ "','" + entity.getPhone()+ "','" + entity.getCountry() + "')");
            q.executeUpdate();
        }
    }

What advantages introduces an approach as presented?

The functionality of the above presented coding example has the following request handling cycle:


Developing application code as in the cycle above presented, introduces the following advantages:

  • data is partitioned across databases  and requests are distributed between two (or more) databases; database workload is balanced between the two databases.
  • no overhead is introduced by frameworks because Java core technologies are used
  • multithreading
  • communication protocols between the databases are flexible; based on the existing database services a wide number of database links can be created.
  • security is being obtained using JSF validation methods (JSF2.0 tag library), but also action controller validation.

Learn more from:

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}