JavaServer Faces Anti-Patterns Dennis Byrne - ThoughtWorks [email_address] http://notdennisbyrne.blogspot.com
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(&quot;start&quot;); if(end == null)  throw new NullPointerException(&quot;end&quot;); if(start.after(end))  throw new IllegalStateException(&quot;start > end&quot;); } }
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=&quot;#{managedBean.value}&quot; converter=&quot;#{threadUnsafe}&quot; /> <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=&quot;#{managedBean.value}&quot; > <f:converter converterId=&quot;threadUnsafe&quot; >  <!-- 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 = &quot;default_class&quot;; protected void setProperties(UIComponent component) { super.setProperties(component); Widget span = (Widget) component; span.setStyleClass(styleClass); span.setTitle(title == null ? &quot;no title&quot; : title); FacesContext ctx = FacesContext.getCurrentInstance(); Map session = ctx.getExternalContext().getSessionMap(); span.setStyle((String) session.get(&quot;style&quot;)); } }
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(&quot;id&quot;); FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext ectx = ctx.getExternalContext(); String id = ectx.getRequestParameterMap().get(&quot;id&quot;);
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=&quot;#{projectBean.projects}&quot; var=&quot;project&quot;> <h:column>   <h:commandLink action=&quot;#{projectBean.viewProject}&quot;  value=&quot;view project&quot;/>  </h:column> <h:column>    <!-- ... and + N trips for each child record -->   <f:facet name=&quot;header&quot;>Project Manager</f:facet>   #{project .manager.name } </h:column> <h:column>   <f:facet name=&quot;header&quot;>Project Name</f:facet>   #{project.name} </h:column> </h:dataTable>
N + 1- N public class OpenTransactionInApplicationPhaseListener  implements PhaseListener { public void beforePhase(PhaseEvent event) { try { ObjectRelationalUtility.startTransaction(); } catch (Throwable throwable) { /* sans error handling */ } } public void afterPhase(PhaseEvent event) { try { ObjectRelationalUtility.commitTransaction();  } catch (Throwable throwable) { ObjectRelationalUtility.rollbackTransaction(); } } public PhaseId getPhaseId(){return  PhaseId.INVOKE_APPLICATION ;} }
Thanks Dennis Byrne - ThoughtWorks [email_address] http://notdennisbyrne.blogspot.com

JavaServer Faces Anti-Patterns and Pitfalls

  • 1.
    JavaServer Faces Anti-PatternsDennis Byrne - ThoughtWorks [email_address] http://notdennisbyrne.blogspot.com
  • 2.
    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>
  • 3.
    Validating Setter publicclass Iteration { private Calendar start, end; // injected // sans setters and getters for start, end public void setLast(String last) { if(start == null) throw new NullPointerException(&quot;start&quot;); if(end == null) throw new NullPointerException(&quot;end&quot;); if(start.after(end)) throw new IllegalStateException(&quot;start > end&quot;); } }
  • 4.
    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 ... } }
  • 5.
    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; } }
  • 6.
    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>
  • 7.
    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>
  • 8.
    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
  • 9.
    Thread Safety <h:inputTextvalue=&quot;#{managedBean.value}&quot; converter=&quot;#{threadUnsafe}&quot; /> <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=&quot;#{managedBean.value}&quot; > <f:converter converterId=&quot;threadUnsafe&quot; > <!-- Always Safe --> </h:inputText> <converter> <converter-id>threadUnsafe</converter-id> <converter-class>org.apache.myfaces.book.ThreadUnsafeConverter </converter-class> </converter>
  • 10.
    Facelets Migration publicclass WidgetTag extends UIComponentELTag{ private String title, styleClass = &quot;default_class&quot;; protected void setProperties(UIComponent component) { super.setProperties(component); Widget span = (Widget) component; span.setStyleClass(styleClass); span.setTitle(title == null ? &quot;no title&quot; : title); FacesContext ctx = FacesContext.getCurrentInstance(); Map session = ctx.getExternalContext().getSessionMap(); span.setStyle((String) session.get(&quot;style&quot;)); } }
  • 11.
    Law of DemeterA “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}
  • 12.
    Vendor Lock-in importorg.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); } }
  • 13.
    Vendor Lock-in Solutionimport 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); } }
  • 14.
    Portlet ClassCastException FacesContextctx = FacesContext.getCurrentInstance(); ExternalContext ectx = ctx.getExternalContext(); ServletRequest request = ( ServletRequest )ectx .getRequest(); String id = request.getParameter(&quot;id&quot;); FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext ectx = ctx.getExternalContext(); String id = ectx.getRequestParameterMap().get(&quot;id&quot;);
  • 15.
    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 */ } } }
  • 16.
    N + 1<!-- One trip to the database for the master record ... --> <h:dataTable value=&quot;#{projectBean.projects}&quot; var=&quot;project&quot;> <h:column> <h:commandLink action=&quot;#{projectBean.viewProject}&quot; value=&quot;view project&quot;/> </h:column> <h:column> <!-- ... and + N trips for each child record --> <f:facet name=&quot;header&quot;>Project Manager</f:facet> #{project .manager.name } </h:column> <h:column> <f:facet name=&quot;header&quot;>Project Name</f:facet> #{project.name} </h:column> </h:dataTable>
  • 17.
    N + 1-N public class OpenTransactionInApplicationPhaseListener implements PhaseListener { public void beforePhase(PhaseEvent event) { try { ObjectRelationalUtility.startTransaction(); } catch (Throwable throwable) { /* sans error handling */ } } public void afterPhase(PhaseEvent event) { try { ObjectRelationalUtility.commitTransaction(); } catch (Throwable throwable) { ObjectRelationalUtility.rollbackTransaction(); } } public PhaseId getPhaseId(){return PhaseId.INVOKE_APPLICATION ;} }
  • 18.
    Thanks Dennis Byrne- ThoughtWorks [email_address] http://notdennisbyrne.blogspot.com