2015 Proof Of Concept: JSF 2.0 Distributed Multitiered Application
Join the DZone community and get the full member experience.
Join For FreeThis 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:
- Generating a JavaServer Faces 2.x CRUD Application from a Database
- JSF Versus JSP, Which One FitsYour CRUD Application Needs? (Part 1)
- JSF Versus JSP, Which One Fits YourCRUD Application Needs? (Part 2)
Opinions expressed by DZone contributors are their own.
Trending
-
4 Expert Tips for High Availability and Disaster Recovery of Your Cloud Deployment
-
Best Practices for Securing Infrastructure as Code (Iac) In the DevOps SDLC
-
Using OpenAI Embeddings Search With SingleStoreDB
-
What ChatGPT Needs Is Context
Comments