I am in a project where I will write a lot of pages like this, so I want to use the most efficient (to write) coding pattern.
Background: In the past I have used CODI's @ViewAccessScoped to preserve state between requests, and more recently I have started using flash scoped objects to save state. I can't use JSF @ViewScoped because I use CDI and they don't play well together. So I want to see if I can do this with only @RequestScoped backing beans.
The page is designed like this (the p namespace is Primefaces):
<f:metadata>
<f:viewParam name="ID" value="#{backing.id}" />
</f:metadata>
....
<h1>Edit Object Page</h1>
<h:form id="formObj" rendered="#{backing.accessOK}">
<p:panelGrid columns="2">
<h:outputLabel value="Field #1:"/>
<p:inputText value="#{backing.record.field1}" />
(more input fields)
<h:outputLabel value="Action:" />
<h:panelGroup>
<p:commandButton value="Save"
action="#{backing.save}"
/>
<p:commandButton value="Cancel" action="backing.cancel" />
</h:panelGroup>
</p:panelGrid>
<p:messages showDetail="true" showSummary="true" />
</h:form>
If the page is requested, the method accessOK() has the ability to keep the h:form from being rendered. Instead, the p:messages is shown with whatever FacesMessage(s) the accessOK() method cares to set.
The pattern for the bean backing looks like this:
@Named
@RequestScoped
public class Backing {
private long id;
private SomeJPAEntity record;
private Boolean accessOK;
public long getId() { return id; }
public void setId(long value) { id = value; }
public boolean accessOK() {
if (accessOK != null) return accessOK;
if (getRecord() == null) {
// add a FacesMessage that explains the record
// does not exist
return accessOK = false; // note single =
}
// do any other access checks, such as write permissions
return accessOK = true;
}
public SomeJPAEntity getRecord() {
if (record != null) return record;
if (getId() > 0) record = // get the record from DB
else record = new SomeJPAEntity();
return record;
}
public String execute() {
if (!accessOK()) return null; // bad edit
// do other integrity checks here. If fail, set FacesMessages
// and return null;
if (getId() > 0) // merge the record back into the data base
else // persist the record
}
}
Here is what goes wrong with this model. When the Save button is clicked, a new instance of Backing is built, and then there are a lot of calls to the getRecord() getter before the setID() setter is called. So the logic in getRecord() breaks because it cannot rely on the id property being valid when it is called.
When this was a @ViewAccessScoped (or ViewScoped) backing bean, then both the id and record properties are already set when the form is processed with the commandButton. Alternatively you can save those properties in flash storage but that has its own problems I want to avoid.
So is there a way to make this programming model work within the specification?