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 }