So the problem appears to be this:
The Sun RI basically makes use of the interaction between 3 things.
1) The FacesContext (implemented by FacesContextImpl and BridgeFacesContext) which provides the API for adding and getting the FacesMessages.
2) The com.sun.faces.util.RequestStateManager which is an internal utility for storing and retrieving "private" request-scoped attributes.
3) The RenderResponsePhase which does some message processing before and after the view is rendered.
Here's the relevant code from RenderResponsePhase.execute:
try {
//Setup message display LOGGER.
if (LOGGER.isLoggable(Level.INFO)) {
Iterator<String> clientIdIter = facesContext.getClientIdsWithMessages();
//If Messages are queued
if (clientIdIter.hasNext()) {
Set<String> clientIds = new HashSet<String>();
//Copy client ids to set of clientIds pending display.
while (clientIdIter.hasNext())
{
clientIds.add(clientIdIter.next());
}
RequestStateManager.set(facesContext,
RequestStateManager.CLIENT_ID_MESSAGES_NOT_DISPLAYED,
clientIds);
}
}
//render the view
facesContext.getApplication().getViewHandler().
renderView(facesContext, facesContext.getViewRoot());
//display results of message display LOGGER
if (LOGGER.isLoggable(Level.INFO) &&
RequestStateManager.containsKey(facesContext,
RequestStateManager.CLIENT_ID_MESSAGES_NOT_DISPLAYED)) {
//remove so Set does not get modified when displaying messages.
Set<String> clientIds = TypedCollections.dynamicallyCastSet(
(Set) RequestStateManager.remove(facesContext,
RequestStateManager.CLIENT_ID_MESSAGES_NOT_DISPLAYED),
String.class);
if (!clientIds.isEmpty()) {
//Display each message possibly not displayed.
StringBuilder builder = new StringBuilder();
for (String clientId : clientIds) {
Iterator<FacesMessage> messages = facesContext.getMessages(clientId);
while (messages.hasNext())
{
FacesMessage message = messages.next();
builder.append("\n");
builder.append("sourceId=").append(clientId);
builder.append("[severity=(").append(message.getSeverity());
builder.append("), summary=(").append(message.getSummary());
builder.append("), detail=(").append(message.getDetail()).append(")]");
}
}
LOGGER.log(Level.INFO, "jsf.non_displayed_message", builder.toString());
}
}
} catch (IOException e)
{
throw new FacesException(e.getMessage(), e);
}
In the RenderResponsePhase, before rendering the view, it retrieves all the messages for the pending client ids with messages and adds them to the RequestStateManager. After the view has been rendered, it checks to see if any of those entries are still there. The actual logic for clearing these pending ids is in the FacesContextImpl.getMessage and getMessages methods. It calls the RequestStateManager to return the pending message(s) and additionally clears it/them.
One of the oddities is that the logic in the RenderResponsePhase is all wrapped in LOGGER.isLoggable(Level.INFO) checks. So none of this will happen if the logging level is set higher. The other oddity is that the act of adding the messages doesn't invoke the RequestStateManager at all so our own BridgeFacesContext does all the message handling fine. It's just that when we retrieve the messages for display, we aren't clearing out this secret stash of messages that was only added by the RenderResponsePhase because of a certain logging level.
Not sure of the best way to fix this as it's not really a functional problem per se. More of a logging anomaly that's relying on secret information to guess that something 'might' be going wrong. The 'WARNING' is actually part of the LogStrings.properties file for the message and not actually the logging level (which is INFO).
I'm seeing this with ICEfaces 1.7 RC1 + JSF 1.2 + JBoss AS 4.2.2