I solved the problem, it was caused by using @RequestScoped instead of @SessionScoped for my page controller bean.
What I was doing was using a edit link from a datatable passing the current product object to the doEditProduct method.
<h:dataTable id="viewProductsTable" value="#{productController.products}" var="_product"
styleClass="data-table" headerClass="table-header" rowClasses="table-row">
<h:column>
<f:facet name ="header">
<h:outputText value="Product Name" />
</f:facet>
<h:outputText value="#{_product.productName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Goal"/>
</f:facet>
<h:outputText value="#{_product.goal}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Details"/>
</f:facet>
<h:outputText value="#{_product.description}"/>
</h:column>
<h:column>
<h:form>
<h:commandLink value="Edit" action="#{productController.doEditProduct(_product)}"/>
</h:form>
</h:column>
</h:dataTable>
the productController.doEditProduct method then opened the edit page
public String doEditProduct(Product product) {
this.product = product;
return "edit-product.xhtml";
the edit page contained a simple edit form
<h:form id="addProductForm">
<h:panelGrid columns="2" columnClasses="top-alignment, top-alignment">
<h:outputLabel for="product-name" value="Product Name"/>
<h:outputText value="#{productController.product.productName}" id="product-name" />
<h:outputLabel for="goal" value="Goal" />
<h:inputTextarea value="#{productController.product.goal}" id="goal" rows="10" cols="100" />
<h:outputLabel for="details" value="Details" />
<h:inputTextarea value="#{productController.product.description}" id="details" rows="20" cols="100"/>
<f:facet name="footer">
<h:panelGroup style="padding-top: 15px; display: inline-block; float: right; white-space: nowrap">
<h:commandButton id="submit" value="Save" action="#{productController.updateProduct()}" class="button" />
<h:button id="Cancel" outcome="index.xhtml" value="Cancel" class="button"/>
</h:panelGroup>
</f:facet>
</h:panelGrid>
</h:form>
At this point the product data was displayed on the page. However when the save button was clicked and ran the update method
public String updateProduct() {
try {
log.log(Level.INFO, "Updating: {0}", product.getGoal());
productFacade.edit(product);
} catch (Exception e) {
log.log(Level.WARNING, e.toString());
String errorMessage = getRootErrorMessage(e);
FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, "could not update product");
facesContext.addMessage(null, m);
return "";
}
return "index.xhtml";
Any field that had was disabled or readonly was set to null. In fact any non input field, anywhere on the page was set to null.
@RequestScope on the controller class meant that the class was being reinstantiated and thus the object stored in product was being assigned a new object in the PostConstruct method. The values in the editable fields were transferred to this new object and the non editable fields were set to null. Merging with the database meant that the data in the fields was deleted.
Using @SessionScoped means that the controller beans continues to exist across page requests. (I needed to remind myself about basic web page lifecycles)
Hope this helps anybody else that has this behaviour.
Graham