JavaServer Faces Anti-Patterns Dennis Byrne - ThoughtWorks [email protected] Validating Setter iterationBean com.thoughtworks.Iteration request start #{sprintBean.currentStart} end #{sprintBean.currentEnd} last hack Validating Setter public class Iteration { private Calendar start, end; // injected // sans setters.

Download Report

Transcript JavaServer Faces Anti-Patterns Dennis Byrne - ThoughtWorks [email protected] Validating Setter iterationBean com.thoughtworks.Iteration request start #{sprintBean.currentStart} end #{sprintBean.currentEnd} last hack Validating Setter public class Iteration { private Calendar start, end; // injected // sans setters.

JavaServer Faces Anti-Patterns
Dennis Byrne - ThoughtWorks
[email protected]
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}
#{requestScopedMap[‘key’]}
// calls get(‘key’)
public class MapTrick implements java.util.Map {
public Object get(Object key) {
return new BusinessLogic().doSomething(key);
}
}
public
public
public
public
public
public
public
public
void clear() { }
boolean containsKey(Object arg) { return false; }
boolean isEmpty() { return false; }
Set keySet() { return null; }
Object put(Object key, Object value) { return null; }
void putAll(Map arg) { }
Object remove(Object arg) { return null; }
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;}