Giampaolo Tomassoni wrote:
Dears,
Ciao Bella ;)
I'm facing a problem with myfaces-1.4 in a framed web app under JBoss Seam
(http://labs.jboss.com/portal/jbossseam) and it seems that the problem needs
upstream support (you).
The problem is the following. When a page with parameters and an <h:form> in it
is first displayed through a GET, myfaces correctly issues the phase events
RESTORE_VIEW, then RENDER_RESPONSE. However, when a subsequent GET attempts showing
the very same page, possibly with different parameters, myfaces invokes the following
phases instead: RESTORE_VIEW, APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS,
UPDATE_MODEL_VALUES, INVOKE_APPLICATION and RENDER_RESPONSE. This basicly means that
myfaces handles the subsequent GET as a form submission, which may not always be the
case.
In my case, in example, the GET parameters are used to specify an item in a table. The
<h:form> is then used to edit some of the fields in that item. When the
<h:form> is submitted through a POST, the GET parameters which are used to identify
the edited item are not supplied in the form content: they are instead automatically
otained by a per-conversation context handled by JBoss Seam.
So, handling a GET with parameters as a form submission drives Seam to simply ignore the
parameters and apply the values stored in the conversation context. The net effect is
that once a page with parameters is displayed, it is not anymore possible to
"switch" to the same page with different parameters.
GET isn't handled any differently from POST in MyFaces, and as far as I
know that's the correct behaviour. GET and POST are just two different
ways of encoding the parameters. In plain html, a form's method can be
set to "GET" or "POST".
<form method="get" action="/my/postback/url">
<input name="data" size="10">
</form>
So I don't believe it's a myfaces bug.
What you are seeing in terms of JSF phases is expected; if view X is
requested by the browser and there is a component tree to restore for
that view, then the phases APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS,
UPDATE_MODEL_VALUES, INVOKE_APPLICATION happen. If there is no existing
component tree for that view, then RESTORE_VIEW does not succeed so
processing skips straight to RENDER_RESPONSE.
When using client-side state saving, the behaviour you want happens
automatically, because the POST will include a hidden field that
contains a serialized component tree (hence RESTORE_VIEW can be
performed). The GET command will not contain that hidden field, so
RESTORE_VIEW will not be possible and myfaces will move directly to
RENDER_RESPONSE.
However when using server-side state saving, the component tree can be
found regardless of whether GET or POST is used, so RESTORE_VIEW succeeds.
As it happens, in the application I'm working on we have what seems to
be a similar problem to you (though we don't use Seam); we are using
server-side state saving and want "GET" requests to show a fresh view of
the page even when the GET refers to the same view we recently rendered.
> Please note that the same doesn't hold when a page with <h:form> is
> invoked by a GET without parameters: it always gets a RESTORE_VIEW
> followed by a RENDER_RESPONSE cycle.
I'm surprised by that; are you quite sure? Either the component tree can
be found or it can't, and a few random parameters aren't going to change
that...
>
> Is there any way to circumvent this problem? Is the way myfaces
> handles GET with parameters a by-design behaviour? Is so, which is the
> purpouse? Is there a way (maybe by mean of some init param) to
> instruct facelets to handle GETs always with a RESTORE_VIEW and
> RENDER_RESPONSE cycle, and not as a form submission?
Our solution is a custom PhaseListener that checks the method property
of the request, and discards any component tree that may have been
retrieved during RESTORE_VIEW (thus forcing a jump to RENDER_RESPONSE).
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId().equals(PhaseId.RESTORE_VIEW)) {
// Never do a postback on GET request.
// Ideally this check would be done in
// before-restore-view, but JSF provides no way for
// code to skip the restore-view processing. We
// therefore need to let the normal restore-view
// take its course, then forcibly override the results
// here :-(.
FacesContext fc = event.getFacesContext();
ExternalContext ec = fc.getExternalContext();
HttpServletRequest hreq = (HttpServletRequest)
ec.getRequest();
if (hreq.getMethod().equals("GET")) {
// discard UIViewRoot created during restoreView
// and use a new one
UIViewRoot viewRoot = fc.getViewRoot();
String viewId = viewRoot.getViewId();
UIViewRoot newView =
fc.getApplication().
getViewHandler().createView(fc, viewId);
newView.setViewId(viewId);
fc.setViewRoot(newView);
fc.renderResponse();
}
}
}
Note that this code checks PhaseId==RESTORE_VIEW because our version of
this listener actually does several things, so is active for all phases.
If your version only returns RESTORE_VIEW as its active phase then this
check is not needed.
Cheers,
Simon