voyent
FacesMessages adding during rendering phase are swallowed  XML
Forum Index -> General Help Go to Page: Previous  1, 2
Author Message
victuriosu

Joined: 26/Nov/2007 00:00:00
Messages: 27
Offline


Thank you very much,

I'm calling directly to the renderManager.requestRender(myBean) in your class, and i'd rather call the requestRender of any bean who implements the renderable interface (I think it's the way you do it).

I don't know how can I know what bean is in action.

It's possible to add a session variable who keep the current bean id to make possible to use it in PhaseListener class in a more generic way.






victuriosu

Joined: 26/Nov/2007 00:00:00
Messages: 27
Offline


Thank you very much,

I'm calling directly to the renderManager.requestRender(myBean) in your class, and i'd rather call the requestRender of any bean who implements the renderable interface (I think it's the way you do it).

I don't know how can I know what bean is in action.

It's possible to add a session variable who keep the current bean id to make possible to use it in PhaseListener class in a more generic way?
ansel1


Joined: 07/Nov/2006 00:00:00
Messages: 85
Offline


My impression is that it doesn't matter which bean you make Renderable. In fact, I think you could create a new bean which does nothing but implement Renderable. I don't think it matters whether the bean you "render" is actually backing anything currently visible or not. It seems to trigger a render pass, which re-renders the whole JSF component tree anyway.

In other words, try creating a new, empty managed bean which does nothing but implements Renderable, and try requesting a render on that. I think that will always cause the whole application to render.
victuriosu

Joined: 26/Nov/2007 00:00:00
Messages: 27
Offline


I have created a renderBean who only implements Renderable and defines necessary methods. I've added it in faces-config.xml with application scope

I call requestRender of this bean in PhaseListener

I put a log message in PhaseListener.afterPhase() and debug the application.

cachedCount is always 0, maybe becouse I set my inputs with partialSubmit=true and then no messages remain cached.

Messages are displayed correctly, I think, but I'm not completely sure. :D

You helped me a lot. Thanks





ansel1


Joined: 07/Nov/2006 00:00:00
Messages: 85
Offline


Yeah, partialSubmits will still make make your faces messages disappear. My phaselistener is designed to clear the message cache on every JSF lifecycle *which was initiated by a servlet request*. This includes partial submits. But that's ok for us, it's the behavior we were looking for.

The situation where it really helps is when you do a lot of server initiated rendering. Say you have a clock on the screen, and use RenderManager to re-render the application every second so your clock appears to update.

Without this phase listener, and faces messages will disappear after a second, because they are cleared every time there is a server-initiated render. My phase listener prevents this. And of course it also deals with the issue of faces messages queued during the render phase.
victuriosu

Joined: 26/Nov/2007 00:00:00
Messages: 27
Offline


Now I understand a little bit more about lifecycle and render phases.

I've got some problems rendering messages in forms, so it has been solved using PhaseListener.

I suppose if I set partialSubmit=false i can show all messages at one time, but I prefer the current behavior.

I really appreciate your help, I think I'll get more advantages of PhaseListener now I know how to force render phases.

Once again, thanks




thewolf


Joined: 04/Jan/2008 00:00:00
Messages: 128
Offline


I do not get this to work.

When I have two browser windows of the same page, one with messages and one without, and the one without does a servlet request which triggers a server initiated rerendering of the other window (the one with the messages), the messages there disappear.

I think the problem is that the cleaning happens for all client ids.

Any suggestions?

Thanks,
Wolfgang
thewolf


Joined: 04/Jan/2008 00:00:00
Messages: 128
Offline


I added code to save the messages seperatly per Request, this does now work:
Code:
package de.whateveryouwant;
 
 import javax.faces.application.FacesMessage;
 import javax.faces.context.FacesContext;
 import javax.faces.event.PhaseEvent;
 import javax.faces.event.PhaseId;
 import javax.faces.event.PhaseListener;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import com.icesoft.faces.async.render.RenderManager;
 
 import java.util.*;
 
 /**
  * This class serves several purposes:
  * 1. Prevent faces messages from disappearing every time there is a server-initiated render.  With this class, faces
  * messages persist until the next client-trigger JSF lifecycle.
  * 2. Render faces messages which are added during the render phase of the JSF lifecycle.  Typically, messages added during the render
  * phase, after the <ice:messages> component has been rendered, will never be displayed.  They aren't displayed in the
  * currend render cycle, because the <ice:messages> component has already been rendered this cycle, and they are cleared
  * at the end of the cycle, so they are no longer present during the next cycle.  This class ensures that these messages
  * are detected and saved, and that a server-initiated render is triggered when they are detected.
  * 3. Prevent messages added during the render phase to cause duplicate messages to be displayed:  Because messages added
  * during the render phase trigger an additional render, often the messages are just posed again during that second render, which
  * in turn causes a third render, etc, in an infinite loop.  And because we persist the faces messages when the render is
  * server-initiated, the messages will accumulate (you'll see more and more and more of them on the screen).  So when we
  * cache messages, we check whether an equivalent message (same message, severity, and clientId) is already cached.  We
  * drop the duplicates.
  * <p/>
  * Here's how we do it, starting at the end of the render phase:
  * 1. At the end of every render phase, we store all the current FacesMessages in a local cache.
  * 2. At the beginning of every apply values phase, we clear the cache.  Apply values phases only occur when the lifecycle
  * was triggered by the client, not by the server.  We only persist faces messages after server-initiated lifecycles.
  * 3. At the beginning of the render phase, we take all the messages in the cache and add them to the FacesContext, avoiding
  * duplicates.  We also count the total number of messages in the context.
  * 4. At the end of the render phase, while we are caching the messages (step 1), we also do another count of the total
  * messages
  */
 public class FacesMessagesPhaseListener implements PhaseListener {
     /**
 	 * 
 	 */
 	private static final long serialVersionUID = -876046660878287376L;
 	
 	private final static Log log = LogFactory.getLog(FacesMessagesPhaseListener.class);
 	
 	private Map<Object, Map<String, Collection<FacesMessage>>> messageCaches
 		= Collections.synchronizedMap(new WeakHashMap<Object, Map<String, Collection<FacesMessage>>>());
 	
 	private Object getKey4Messages(final FacesContext context) {
 		return context.getExternalContext().getRequest();
 	}
 	
 	private Map<String, Collection<FacesMessage>> getMessagesCache(final FacesContext context) {
 		final Object key = getKey4Messages(context);
 		Map<String, Collection<FacesMessage>> messageCache = messageCaches.get(key);
 		if (messageCache == null) {
 			messageCache = Collections.synchronizedMap(new HashMap<String, Collection<FacesMessage>>());
 			messageCaches.put(key, messageCache);
 		}
 		return messageCache;
 	}
 	
 	private void removeMessagesCache(final FacesContext context) {
 		messageCaches.remove(getKey4Messages(context));
 	}
 	
     public synchronized void afterPhase(PhaseEvent event) {
         if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
             // Cache messages.
             FacesContext context = event.getFacesContext();
             int cachedCount = cacheMessages(context, false);
 
             // Messages are cached both before and after the render phase.
             // If the cachedCount is greater than zero, it means a message was cached here at the end
             // of the render phase which was not cached at the beginning of the phase, so it must have
             // been added during the render phase.  Messages like this may not have been rendered, so
             // trigger a re-render.
 
             // Also, if the current total number of cached messages is greater than the number of messages
             // currently in the context, that means the faces messages in the context were cleared sometime during
             // the render cycle.  This occurs when some one thread calls FacesContext.release() while another thread is
             // executing the JSF lifecycle.  This also usually means some messages were not rendered, do trigger a
             // re-render.
             if (cachedCount > 0 || getCachedMessagesCount(context) > getContextMessagesCount(context)) {
                 // Messages were added during the render phase.  Repaint to display
                 // the new messages.
             	
             	//adapt to your async render call method
             	log.info("requestRender");
             	RenderManager.getInstance().getOnDemandRenderer("Renderername").requestRender();
             }
         }
     }
 
     private int cacheMessages(FacesContext context, boolean clearMessages) {
         // This is a count of all messages already cached, plus any new messages we cache
         int cachedCount = 0;
         Iterator<String> clientIdsWithMessages = context.getClientIdsWithMessages();
         while (clientIdsWithMessages.hasNext()) {
             String clientId = clientIdsWithMessages.next();
             Iterator<FacesMessage> iterator = context.getMessages(clientId);
             Collection<FacesMessage> cachedMessages = getMessagesCache(context).get(clientId);
             if (cachedMessages == null) {
                 cachedMessages = new TreeSet<FacesMessage>(new FacesMessageComparator());
                 getMessagesCache(context).put(clientId, cachedMessages);
             }
             while (iterator.hasNext()) {
                 FacesMessage facesMessage = iterator.next();
                 if (clearMessages) {
                     iterator.remove();
                 }
                 // Check to make sure the message was actually cached.  If not cached, then the message
                 // was already in the cache set, and should not count toward the message count.  This prevents
                 // the same message being added every time we go through the render phase.
                 if (cachedMessages.add(facesMessage)) {
                     cachedCount++;
                 }
             }
         }
         return cachedCount;
     }
 
     public synchronized void beforePhase(PhaseEvent event) {
         if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
             FacesContext context = event.getFacesContext();
 
             // Cache messages, clearing all messages out of the faces context as we go.
             // Track the number of messages, so we can compare it to the number of messages after rendering
             cacheMessages(context, true);
 
             // Resubmit cached messages.
             if (!getMessagesCache(context).isEmpty()) {
                 for (String clientId : getMessagesCache(context).keySet()) {
                     for (FacesMessage message : getMessagesCache(context).get(clientId)) {
                         context.addMessage(clientId, message);
                     }
                 }
             }
         } else if (event.getPhaseId() == PhaseId.APPLY_REQUEST_VALUES) {
             // Clear cached messages for this request
         	removeMessagesCache(event.getFacesContext());
         }
     }
 
     private int getCachedMessagesCount(final FacesContext context) {
         int count = 0;
         for (Collection<FacesMessage> messages : getMessagesCache(context).values()) {
             count += messages.size();
         }
         return count;
     }
 
     private int getContextMessagesCount(FacesContext context) {
         Iterator<FacesMessage> iterator = context.getMessages();
         int count = 0;
         while (iterator.hasNext()) {
             iterator.next();
             count++;
         }
         return count;
     }
 
     public PhaseId getPhaseId() {
         return PhaseId.ANY_PHASE;
     }
 
     /**
      * We cache the messages in a treeset, using this comparator.  This helps avoid
      * duplication faces messages which are added every time through the render cycle.
      */
     private static class FacesMessageComparator implements Comparator<FacesMessage> {
         public int compare(FacesMessage o1, FacesMessage o2) {
             int compareValue = safeCompare(o1, o2);
             if (compareValue == 0) {
                 compareValue = safeCompare(o1.getSeverity(), o2.getSeverity());
             }
             if (compareValue == 0) {
                 compareValue = safeCompare(o1.getSummary(), o2.getSummary());
             }
             if (compareValue == 0) {
                 compareValue = safeCompare(o1.getDetail(), o2.getDetail());
             }
             return compareValue;
         }
 
         @SuppressWarnings("unchecked")
         private int safeCompare(Object o1, Object o2) {
             if (o1 == o2) {
                 return 0;
             }
             if (o1 == null) {
                 return -1;
             } else if (o2 == null) {
                 return 1;
             }
             if (o1 instanceof Comparable && o2.getClass() == o1.getClass()) {
                 return ((Comparable) o1).compareTo(o2);
             }
             return 0;
         }
     }
 }
thewolf


Joined: 04/Jan/2008 00:00:00
Messages: 128
Offline


BTW does anybody know of a convenient way to request a async rendering for the current view only inside a Phaselistener?
geoff2k

Joined: 02/Nov/2007 00:00:00
Messages: 2
Offline


thewolf wrote:

I added code to save the messages seperatly per Request, this does now work:
 


The above code works perfectly under IceFaces 1.7.0 and 1.7.1, but under 1.7.2, the assumption that

Code:
  * Apply values phases only occur when the lifecycle was triggered by the client, not by the server.
 


No longer seems to hold true, under 1.7.2, all requests (client-initiated AND server-initiated) seem to trigger APPLY_REQUEST_VALUES. :-(

Is there another way within a Phase Listener of distinguishing between client-initiated requests and server-initiated requests?
ansel1


Joined: 07/Nov/2006 00:00:00
Messages: 85
Offline


I submitted a customer support request about this. There are a couple ways, though neither are great. Hopefully in the next release they will implement a flag or something to detect this more reliably.

Anyway, method one is look at the thread stacktrace and look for classes that are only in server-side renders. I think RunnableRender is one of them.

Method 2 is look for a key/value pair in the request params map: "javax.faces.ViewState" = "ajaxpush"

This is some internal think IceFaces only sets for server-side renders to make something in Seam work. But it's only set for JSF 1.2, so that limits its usefulness.
geoff2k

Joined: 02/Nov/2007 00:00:00
Messages: 2
Offline


ansel1 wrote:
Method 2 is look for a key/value pair in the request params map: "javax.faces.ViewState" = "ajaxpush"

This is some internal think IceFaces only sets for server-side renders to make something in Seam work. But it's only set for JSF 1.2, so that limits its usefulness. 


Thanks very much for replying!

As my app uses JSF 1.2, I think I'll go with approach #2; it seems like even though it is internal, it appears to be required for Seam interop, so is unlikely to go away anytime soon... however, I agree that it would be very useful for server-side renders to be made completely explicit for cases such as these.

For those of you following along from home, the above key value pair were added as part of ICE-3532 which involved the following changesets: r17584, r17585.
thewolf


Joined: 04/Jan/2008 00:00:00
Messages: 128
Offline


geoff2k wrote:

ansel1 wrote:
Method 2 is look for a key/value pair in the request params map: "javax.faces.ViewState" = "ajaxpush"

This is some internal think IceFaces only sets for server-side renders to make something in Seam work. But it's only set for JSF 1.2, so that limits its usefulness. 


Thanks very much for replying!

As my app uses JSF 1.2, I think I'll go with approach #2; it seems like even though it is internal, it appears to be required for Seam interop, so is unlikely to go away anytime soon... however, I agree that it would be very useful for server-side renders to be made completely explicit for cases such as these. 


Does somebody already have code for method 1 or 2 and is willing to share the code here?
maiago

Joined: 29/Dec/2008 00:00:00
Messages: 2
Offline


I need to use the PhaseListener to show user messages in a <ice:messages> tag.
A piece of my page is shown below: I simply want to display some database data using a tree, and if any data access throws Exceptions, put an alert message on the top of the portlet.

<ice:portlet>
<ice:form>
<ice:messages globalOnly="true" showDetail="true" />
<ice:panelCollapsible id="categories" expanded="true">
<f:facet name="header">
<ice:panelGroup>
<ice:outputText id="linkHeader" value="#{msgs['filter.categories.title']}" />
</ice:panelGroup>
</f:facet>
<ice:tree id="categoriesTree" value="#{categoriesTreeBean.model}" var="item" hideRootNode="false" hideNavigation="false">
<ice:treeNode>
<f:facet name="content">
<ice:panelGroup style="display: inline">
<ice:outputText value="#{item.userObject.text}" />
</ice:panelGroup>
</f:facet>
</ice:treeNode>
</ice:tree>
</ice:panelCollapsible>
</ice:form>
</ice:portlet>

I'm a newbie, but I think it's just the situation described in the thread.

My question is relative to the "NavigationBean"...
For simplicity I replace it with the tree controller bean, declared as Renderable and Disposable and it works.

...
CategoriesTreeBean renderBean = (CategoriesTreeBean)FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("categoriesTreeBean");
renderBean.render();
...

where renderBean.render() content is:
...
OnDemandRenderer onDemandRenderer=getRenderManager().getOnDemandRenderer(CATEGORIES_RENDER_GROUP);
onDemandRenderer.requestRender();
...

What if I'd like to generalize the PhaseListener, making it indipendent from application specific beans?
Sorry for perhaps stupid question, but I'm still trying to understand ajax push technique...
 
Forum Index -> General Help Go to Page: Previous  1, 2
Go to:   
Powered by JForum 2.1.7ice © JForum Team