001    package edu.nrao.sss.webapp.faces;
002    
003    import javax.faces.application.FacesMessage;
004    import javax.faces.context.FacesContext;
005    import javax.faces.event.PhaseEvent;
006    import javax.faces.event.PhaseId;
007    import javax.faces.event.PhaseListener;
008    import javax.servlet.http.HttpServletRequest;
009    
010    import org.apache.log4j.Logger;
011    
012    import java.util.Enumeration;
013    import java.util.Iterator;
014    import java.util.HashMap;
015    import java.util.Map;
016    import java.util.ArrayList;
017    import java.util.List;
018    
019    /**
020     * This class attempts to retain FacesMessages that would other wise be lost
021     * due to a panelDivider being resized.  This class only listens to events for
022     * the RENDER_RESPONSE phase.  During the afterPhase() method, we cache all
023     * existing FacesMessages in a Map keyed by the associated HttpSession's Id.
024     * During the beforePhase() method, the request parameters are searched for a
025     * parameter name ending in {@code InPercent}.  If such a parameter exists and
026     * its value is an integer, then the messages found in the cache are added to
027     * the FacesContext.  Either way, the cache is cleared at the end of the
028     * beforePhase() method to make sure we're not holding onto stale
029     * FacesMessages.
030     */
031    public class FacesMessagePhaseListener implements PhaseListener
032    {
033      private static final Logger log = Logger.getLogger(FacesMessagePhaseListener.class);
034    
035      private static final String MESSAGES_WITH_NO_CLIENT_ID = "MESSAGES_WITH_NO_CLIENT_ID";
036    
037      // Map<sessionid, Map<componentId, List<Messages for component>>>
038      private Map<String, Map<String, List<FacesMessage>>> cache = new HashMap<String, Map<String, List<FacesMessage>>>();
039    
040      public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; }
041    
042      /**
043       * If the request has a parameter ending in "InPercent" defined to a
044       * non-empty string (an integer to be exact), then add the cached
045       * FacesMessages back to the FacesContext.  Either way, empty the cache.
046       */
047      public synchronized void beforePhase(PhaseEvent event)
048      {
049        FacesContext c = event.getFacesContext();
050        HttpServletRequest req = (HttpServletRequest)c.getExternalContext().getRequest();
051    
052        for (Object key : req.getParameterMap().keySet())
053        {
054          String name = (String)key;
055          if (name.endsWith("InPercent"))
056          {
057            String val = req.getParameter(name);
058            if (val != null)
059            {
060              try
061              {
062                Integer.parseInt(val);
063                restoreMessagesFromCache(c);
064                break;
065              }
066    
067              // Ignore value if it is not a valid integer.
068              catch (NumberFormatException nfe) {}
069            }
070          }
071        }
072    
073        clearMessageCache(c);
074      }
075    
076      /**
077       * Always caches all FacesMessages for possible later use in the beforePhase
078       * method.
079       */
080      public synchronized void afterPhase(PhaseEvent event)
081      {
082        cacheMessages(event.getFacesContext());
083      }
084    
085      /**
086       * Returns the sessionid of the HttpSession associated with {@code c}.
087       */
088      private String getSessionId(FacesContext c)
089      {
090        HttpServletRequest req = (HttpServletRequest)c.getExternalContext().getRequest();
091        return req.getSession().getId();
092      }
093    
094      /**
095       * Caches all FacesMessages in {@code c} in our local Map.  NOTE: cache for c
096       * is expected to be empty before this is called!  If it isn't, duplicate
097       * messages may be added or messages may be overwritten or lost.
098       */
099      private void cacheMessages(FacesContext c)
100      {
101        String sessionIdKey = getSessionId(c);
102    
103        // Map<componentId, list of messages for componentId>
104        Map<String, List<FacesMessage>> messages = new HashMap<String, List<FacesMessage>>();
105    
106        for (Iterator idIt = c.getClientIdsWithMessages(); idIt.hasNext();)
107        {
108          String clientId = (String)idIt.next();
109          String idKey = clientId;
110    
111          if (clientId == null)
112            idKey = MESSAGES_WITH_NO_CLIENT_ID;
113    
114          List<FacesMessage> msgsForId = new ArrayList<FacesMessage>();
115          for (Iterator msgIt = c.getMessages(clientId); msgIt.hasNext();)
116            msgsForId.add((FacesMessage)msgIt.next());
117    
118          messages.put(idKey, msgsForId);
119        }
120    
121        if (cache.put(sessionIdKey, messages)!= null)
122        {
123          // Possible error? do we care?
124        }
125      }
126    
127      /**
128       * If there are messages cached for {@code c}, then they are added to {@code c}.
129       * No effort is taken to make sure there are no duplicate messages!
130       */
131      private void restoreMessagesFromCache(FacesContext c)
132      {
133        // Map<componentId, list of messages for componentId>
134        Map<String, List<FacesMessage>> messages = cache.get(getSessionId(c));
135    
136        if (messages != null && !messages.isEmpty())
137        {
138          for (String idKey : messages.keySet())
139          {
140            String clientId = idKey;
141            if (idKey == MESSAGES_WITH_NO_CLIENT_ID)
142              clientId = null;
143    
144            for (FacesMessage msg : messages.get(idKey))
145              c.addMessage(clientId, msg);
146          }
147        }
148      }
149    
150      /**
151       * Clears all cached items related to {@code c}.
152       */
153      private void clearMessageCache(FacesContext c)
154      {
155        cache.remove(getSessionId(c));
156      }
157    }