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 & 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:
- The prefix h_ is js standard Mojarra library and the p_ is for Primefaces (in type parameter).
- 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!