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.json1 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
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!
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