lunes, 2 de octubre de 2017

JEE & JSF15th Part: Creating an abstraction view layer to JSF components and Forms (4/5). Component builder and form definition

0. Introduction

Once we have loaded all components and attributes catalog by means of a Singleton class in the previous entry, it is interesting to have a class that reads a json file that describes how components are displayed in a fom and creates all components.

Now we need an new entry to pom.xml referencing to Apache Commons BeanUtils that helps us to instantiate and assign value to an object programmatically.

1. Pom.xml file


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.ximodante.jsf</groupId>
  <artifactId>JSFv01</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>JSFv01</name>
  <description>JSF 2.2 &amp; CDI</description>
 
  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>
  
  <dependencies>
     
    <!-- Servlet 3.1 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    
    <!-- To solve Tomcat problem : java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    
    
    <!--  JSF 2.2 API -->
    <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-api</artifactId>
     <version>2.2.14</version>
    </dependency>

    <!--  JSF 2.2 Implementation -->
    <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-impl</artifactId>
     <version>2.2.14</version>
    </dependency>

    <!--  Primefaces -->
    <dependency>
     <groupId>org.primefaces</groupId>
     <artifactId>primefaces</artifactId>
     <version>6.1</version>
    </dependency>

    <!--  Primefaces Themes -->
    <dependency>
     <groupId>org.primefaces.extensions</groupId>
     <artifactId>all-themes</artifactId>
     <version>1.0.8</version>
     <type>pom</type>
    </dependency>
    
    <!-- Weld CDI for Tomcat (does not fulfill all capabilities !!!) -->
    <dependency>
      <groupId>org.jboss.weld.servlet</groupId>
      <artifactId>weld-servlet-shaded</artifactId>
      <version>3.0.0.Final</version>
    </dependency>
    
    <!-- Validation API Optional -->
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>2.0.0.CR3</version>
    </dependency>
        
    <!-- Hibernate Bean Validator Optional -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.4.1.Final</version>
    </dependency>
    
    <!--  Lombok for setters and getters -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.18</version>
    </dependency>
    
    <!-- JSON -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>

    <!-- Apache Commons Utils -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.6</version>
    </dependency>
    
    <!-- Apache Commons Text -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-text</artifactId>
      <version>1.1</version>
    </dependency>
    
    <!--  Apache commons Beanutils for conversion of types-->
    <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.3</version>
    </dependency>
    
    
    <!--  JPA Hibernate -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.2.10.Final</version>
    </dependency>
    
    <!--  PostgreSQL -->
    <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>9.4.1212</version>
    </dependency>
    
    
    <!-- Primeface Extensions -->
    <!-- https://mvnrepository.com/artifact/org.primefaces.extensions/primefaces-extensions -->
    <dependency>
      <groupId>org.primefaces.extensions</groupId>
      <artifactId>primefaces-extensions</artifactId>
      <version>6.1.1</version>
    </dependency>
    
    <!--  for evaluating EL expressions -->
    <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api 16/9/2017-->
    <dependency>
      <groupId>javax.el</groupId>
      <artifactId>javax.el-api</artifactId>
      <version>3.0.1-b04</version>
    </dependency>

  </dependencies> 
  
</project>

2. Form definition file in json

Let's create a sample form definition file in JSON in the src/main/resources/forms. We can have as many forms descriptions files a needed. In this case, the name is form-01-test.json


1
2
3
4
5
6
{ "name" : "main", "type" : "h_HtmlPanelGroup", "layout": "block" ,"children": 
  [ { "name": "Full Name"  , "type": "p_InputText" ,"value":"#{testComponentBean.fullName}" }, 
    { "name": "Color"      , "type": "p_InputText" ,"value":"Red"                    , "disabled":"true"},
    { "name": "Comment"    , "type": "p_InputText" ,"value":"This is a simple example" }
  ]   
}  

As we can see 4 components are defined: There is a container panel that includes 3 input text components.

Notes:
  1. The prefix h_ is js standard Mojarra library and the p_ is for Primefaces (in type parameter).
  2. There is a component that accepts and expresion (#
    {testComponentBean.fullName}
    )

3. ComponentBuilder class

In this class, we will make extense use of Reflection and  BeanUtils. It creates components programmatically and assigns properties distinguishing between string constants and EL expressions and actions.

This component builder retrieves information either from a JSON file or a Map structure.

Here is the code. Note the huge quantity of exceptions that can be thrown

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package org.ximodante.jsf.component;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.component.UIComponent;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.beanutils.ConvertUtils;
import org.ximodante.utils.jsf.JSFUtils;
import org.ximodante.utils.json.JsonUtils;
import org.ximodante.utils.string.StrUtils;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.sun.faces.util.ReflectionUtils;

@Named
@ApplicationScoped
/**
 * JSF component builder from information stored in :
 *   A Java Map or 
 *   A JSON File as a map structure
 *   
 * @author Ximo Dante
 *
 */
public class ComponentBuilder implements Serializable {
 
 private static final long serialVersionUID = 1L;
 
 @Inject 
 private ComponentParams cParams;
 
 /**
  * Read a form.json
  * @param map
  * @param parent
  * @param level
  * @throws ComponentJsfException
  * @throws ClassNotFoundException
  * @throws NoSuchMethodException
  * @throws SecurityException
  * @throws InstantiationException
  * @throws IllegalAccessException
  * @throws IllegalArgumentException
  * @throws InvocationTargetException
  */
 
 public void getComponentFromMap(Map<String,Object> map, UIComponent parent, int level) throws ComponentJsfException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  
  String type=null;
  try {
   type=((String)map.get("type")).trim();
  } catch ( NullPointerException e) {
   throw new ComponentJsfException("Attribute \"type\" not Found in json form file");
  }
  //System.out.println("type="+type);
  
  String qname=null;
  try {
   qname=((String)cParams.getAllComponents().get(type).getQclass()).trim();
  } catch ( NullPointerException e) {
   throw new ComponentJsfException("Component type " + type + " not Found in component-types.json file");
  }
  //System.out.println("qname="+qname);
  
  Class<?> componentClass = ReflectionUtils.lookupClass(qname);
  //System.out.println("componentClass="+qname);
  
  Constructor<?> ctor = componentClass.getConstructor();
  //System.out.println("constructor");
  
  UIComponent component = (UIComponent) ctor.newInstance();
  //System.out.println(component.toString());
  
  
  
  for(String s: map.keySet()) {
   ComponentAttribute cAtt=cParams.getAllAttributes().get(s);
   if (cAtt==null) throw new ComponentJsfException("Component attribute " + s + " not Found in component-attributes.json file");
   
   Object value=map.get(s);
   
   switch (cAtt.getType()) {
    case ARRAY:
     if (s.equals("children")) { 
      for (Map <String,Object> cmap: (List<Map<String,Object>>) map.get(s)) {
       System.out.println("child definition......");
       getComponentFromMap(cmap, component, level+1);
      } 
     } else  throw new ComponentJsfException("Component attribute " + s + " of ARRAY type has unknow treatment in ComponentBuilder class");
     break;
   
    case PROPERTY:
     // If an EL Expression is used, it should be set by setValueExpression method 
     if (value.toString().trim().startsWith("#{")) {
            
      // setValueExpresion has two arguments:
      //  1. propertyName that is a string (in this case is "value"
      //  2. ValueExpression ( in this case "#{panelGroupBean.nom}" in test case)
     
      String methodName="setValueExpression";
      ValueExpression vExp=JSFUtils.createValueExpression(value.toString());
      
      Method method = componentClass.getMethod(methodName, String.class, ValueExpression.class);
      method.invoke(component, cAtt.getName(),vExp);
      System.out.println("Invoked Method Name="+ methodName + "with param type:" + cAtt.getPclass() + " param="  + map.get(s) );
      
     // a normal property assignment
     } else {
      
      // Class of the parameter
      Class<?> pclass=  ReflectionUtils.lookupClass(cAtt.getPclass());
      
      // Method of pattern "setPropertyName" 
      String methodName="set"+ StrUtils.CapitalizeFirst(cAtt.getName());
      
      Method method = componentClass.getMethod(methodName, pclass);
      //System.out.println("METODO: " + method.getName() + "  CLASS: "+pclass.getName() + "  VALUE:" + value);
      method.invoke(component, ConvertUtils.convert(value, pclass));
      //System.out.println("Invoked Method Name="+ methodName + "with param type:" + cAtt.getPclass() + " param="  + map.get(s) );
     }
     break;
     
    case ACTION:
     // Class of the parameter
     Class<?> pclass=  ReflectionUtils.lookupClass(cAtt.getPclass());
     
     // setValueExpresion has two arguments:
     //  1. propertyName that is a string (in this case is "value"
     //  2. ValueExpression ( in this case "#{panelGroupBean.nom}" in test case)
    
     String methodName="setActionExpression";
     MethodExpression vExp=JSFUtils.createMethodExpression(value.toString(), String.class, new Class<?>[]{});
     
     Method method = componentClass.getMethod(methodName, ValueExpression.class);
     method.invoke(component, cAtt.getName(),vExp);
     //System.out.println("Invoked Method Name="+ methodName + "with param type:" + cAtt.getPclass() + " param="  + map.get(s) ); 
     break;
   }
    
  }
  parent.getChildren().add(component);
  
 }
 
 /**
  * Gets a component from information obtained in a json file
  * @param isRelativeToResourceFolder
  * @param fileName
  * @param component
  * @throws JsonParseException
  * @throws JsonMappingException
  * @throws IOException
  * @throws ClassNotFoundException
  * @throws NoSuchMethodException
  * @throws SecurityException
  * @throws InstantiationException
  * @throws IllegalAccessException
  * @throws IllegalArgumentException
  * @throws InvocationTargetException
  * @throws ComponentJsfException
  */
 public void getComponentFromJsonFile(boolean isRelativeToResourceFolder, String fileName, UIComponent component) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ComponentJsfException {
  Map<String,Object>mp= JsonUtils.readMap(isRelativeToResourceFolder, fileName);
  getComponentFromMap(mp, component, 0);
 }
 
}

4. Testing

This is the simple facelets file for testing all previous stuff (testpage02-PanelGroup.xhtml in the webapp/pages folder)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:f="http://java.sun.com/jsf/core" 
    xmlns:p="http://primefaces.org/ui">  
    <h:head>  
    </h:head>  
      
    <h:body>  
       <h:form>  
           <h3>
             This file is for testing Primafaces using a Bean (TestComponentBean)
           </h3>  
           <h2>
             In form/form-01-test1.json a value property is set to a Bean-Property. and it is assigned programmatically in TestComponentBean
           </h2> 
           <h:panelGroup binding="#{testComponentBean.pg}" /> 
           <p:commandButton value="Submit"/>
       </h:form>
    </h:body>  
</html>

And this bean will be used for binding the HtmlPanelGroup in the previous facet file 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.ximodante.jsf.component.test;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;

import javax.annotation.PostConstruct;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.ximodante.jsf.component.ComponentBuilder;
import org.ximodante.jsf.component.ComponentJsfException;

import lombok.Getter;
import lombok.Setter;

/**
 * Uses xhtml page
 * @author Ximo Dante
 *
 */

@Named
@ViewScoped
public class TestComponentBean implements Serializable{
 private static final long serialVersionUID = 1L;

 @Getter @Setter private HtmlPanelGroup pg = new HtmlPanelGroup();
 @Getter @Setter private String fullName="Ximo Dante";
 
 
 @Inject
 ComponentBuilder cp;
 
 @PostConstruct
    public void init() {
  pg.setLayout("block");
   
  
  // Programmatically create child component by reflection from a json file
  try {
   cp.getComponentFromJsonFile(true, "forms/form-01-test.json", pg);
  } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
    | IllegalAccessException | IllegalArgumentException | InvocationTargetException | IOException
    | ComponentJsfException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
}

If we right-click on the xhtml file and run on server we get this screen


Obviously, this is an ugly page but it is really important to note that it shows everything we have enclosed in the jsf file!

No hay comentarios:

Publicar un comentario

JEE & JSF16th Part: Creating an abstraction view layer to JSF components and Forms (5/5). Frequent problems

1. ERROR #1: Using a bean that does not exists In the previos entry we used this facelet file: 1 2 3 4 5 6 7 8 9 10 11 1...