170 likes | 187 Views
Learn about common mistakes in JavaServer Faces like use of invalid setters and find solutions to implement proper validation.
E N D
JavaServer Faces Anti-Patterns Dennis Byrne - ThoughtWorks dennisbyrne@apache.org
Validating Setter <managed-bean> <managed-bean-name>iterationBean</managed-bean-name> <managed-bean-class>com.thoughtworks.Iteration </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>start</property-name> <value>#{sprintBean.currentStart}</value> </managed-property> <managed-property> <property-name>end</property-name> <value>#{sprintBean.currentEnd}</value> </managed-property> <managed-property> <property-name>last</property-name> <value>hack</value> </managed-property> </managed-bean>
Validating Setter public class Iteration { private Calendar start, end; // injected // sans setters and getters for start, end public void setLast(String last) { if(start == null) throw new NullPointerException("start"); if(end == null) throw new NullPointerException("end"); if(start.after(end)) throw new IllegalStateException("start > end"); } }
Validating Setter Solutions <application> <variable-resolver> org.springframework.web.jsf.DelegatingVariableResolver </variable-resolver> </application> <application><el-resolver> org.apache.myfaces.el.unified.resolver.GuiceResolver </el-resolver> </application> public class Iteration { private Calendar start, end; // injected @PostConstruct public void initialize() { // domain validation logic here ... } }
The Map Trick #{requestScopedMap.key} // calls get(‘key’) #{requestScopedMap[‘key’]} public class MapTrick implements java.util.Map { public Object get(Object key) { return new BusinessLogic().doSomething(key); } public void clear() { } public boolean containsKey(Object arg) { return false; } public boolean isEmpty() { return false; } public Set keySet() { return null; } public Object put(Object key, Object value) { return null; } public void putAll(Map arg) { } public Object remove(Object arg) { return null; } public int size() { return 0; } }
déjà vu PhaseListener <context-param> <description> comma separated list of JSF conf files </description> <param-name>javax.faces.CONFIG_FILES</param-name> <param-value> /WEB-INF/faces-config.xml </param-value> </context-param> <lifecycle> <phase-listener> com.thoughtworks.PhaseListenerImpl </phase-listener> </lifecycle>
XML Hell <navigation-rule><from-view-id>/home.xhtml</from-view-id> <navigation-case> <from-outcome>contact_us</from-outcome> <to-view-id>/contact.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule><from-view-id>/site_map.xhtml</from-view-id> <navigation-case> <from-outcome>contact_us</from-outcome> <to-view-id>/contact.xhtml</to-view-id> </navigation-case> </navigation-rule> <navigation-rule><from-view-id>*</from-view-id> <navigation-case> <!-- global nav rule --> <from-outcome>contact_us</from-outcome> <to-view-id>/contact.xhtml</to-view-id> </navigation-case> </navigation-rule>
Thread Safety • javax.faces.event.PhaseListener • javax.faces.render.Renderer • Managed Beans • javax.faces.convert.Converter • javax.faces.validator.Validator • javax.faces.FacesContext • JSF Tags
Thread Safety <h:inputText value="#{managedBean.value}" converter="#{threadUnsafe}" /> <managed-bean> <managed-bean-name>threadUnsafe</managed-bean-name> <managed-bean-class> org.apache.myfaces.book.ThreadUnsafeConverter </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <h:inputText value="#{managedBean.value}" > <f:converter converterId="threadUnsafe" > <!-- Always Safe --> </h:inputText> <converter> <converter-id>threadUnsafe</converter-id> <converter-class>org.apache.myfaces.book.ThreadUnsafeConverter </converter-class> </converter>
Facelets Migration public class WidgetTag extends UIComponentELTag{ private String title, styleClass = "default_class"; protected void setProperties(UIComponent component) { super.setProperties(component); Widget span = (Widget) component; span.setStyleClass(styleClass); span.setTitle(title == null ? "no title" : title); FacesContext ctx = FacesContext.getCurrentInstance(); Map session = ctx.getExternalContext().getSessionMap(); span.setStyle((String) session.get("style")); } }
Law of Demeter • A “Train Wreck” - sensitive to changes in domain model employee.getDepartment().getManager() .getOffice().getAddress().getZip(); • An EL “Train Wreck” - sensitive to changes in domain model #{employee.department.manager.office.address.zip} • Encapsulated, insensitive to changes in domain model #{employee.officeManagersZipCode}
Vendor Lock-in import org.apache.myfaces.component.html.ext.HtmlInputHidden; import org.apache.myfaces.component.html.ext.HtmlInputText; import org.apache.myfaces.component.html.ext.HtmlOutputText; public class ImplementationDependentManagedBean { private HtmlInputText input ; private HtmlInputHidden hidden ; private HtmlOutputText output ; /* getters and setters omitted */ public boolean recordTotal(ActionEvent event) { long total = ((Long)input.getValue()).longValue(); total += ((Long)hidden.getValue()).longValue(); total += ((Long)output.getValue()).longValue(); return new JmsUtil().broadcastTotal(total); } }
Vendor Lock-in Solution import javax.faces.component.ValueHolder; public class RefactoredManagedBean { private ValueHolder input ; private ValueHolder hidden ; private ValueHolder output ; /* getters & setters ommitted */ public boolean recordTotal(ActionEvent event) { long total = 0; ValueHolder[] vh = new ValueHolder[] {input, hidden, output}; for(ValueHolder valued : vh) total += ((Long)valued.getValue()).longValue(); return new JmsUtil().broadcastTotal(total); } }
Portlet ClassCastException FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext ectx = ctx.getExternalContext(); ServletRequest request = (ServletRequest)ectx .getRequest(); String id = request.getParameter("id"); FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext ectx = ctx.getExternalContext(); String id = ectx.getRequestParameterMap().get("id");
OpenTransactionInViewFilter public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ try { ObjectRelationalUtility.startTransaction(); chain.doFilter(request, response); ObjectRelationalUtility.commitTransaction(); } catch (Throwable throwable) { try { ObjectRelationalUtility.rollbackTransaction(); } catch (Throwable _throwable) { /* sans error handling */ } } }
N + 1 <!-- One trip to the database for the master record ... --> <h:dataTable value="#{projectBean.projects}" var="project"> <h:column> <h:commandLink action="#{projectBean.viewProject}" value="view project"/> </h:column> <h:column> <!-- ... and + N trips for each child record --> <f:facet name="header">Project Manager</f:facet> #{project.manager.name} </h:column> <h:column> <f:facet name="header">Project Name</f:facet> #{project.name} </h:column> </h:dataTable>
N + 1- N public class OpenTransactionInApplicationPhaseListener implements PhaseListener { public void afterPhase(PhaseEvent event) { try { ObjectRelationalUtility.startTransaction(); } catch (Throwable throwable) { /* sans error handling */ } } public void beforePhase(PhaseEvent event) { try { ObjectRelationalUtility.commitTransaction(); } catch (Throwable throwable) { ObjectRelationalUtility.rollbackTransaction(); } } public PhaseId getPhaseId(){return PhaseId.INVOKE_APPLICATION;} }