/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/sections/trunk/sections-app/src/java/org/sakaiproject/tool/section/jsf/DivMessagesRenderer.java $
* $Id: DivMessagesRenderer.java 105080 2012-02-24 23:10:31Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.tool.section.jsf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIMessages;
import javax.faces.context.FacesContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Work around JSF 1.1's inadequate Messages renderer, which has the following problems:
*
* - Contrary to the documentation, an HTML list is not used to render a list. Instead,
* the message texts are written out one after another.
*
* - There's no way to define styles or classes for the containing HTML element.
*
* - Each message is put into a "span" element rather than a "div". This means that
* important formatting options are not available to page designers.
*
* Of these, the worst problem seems the use of "span" instead of "div". Not many friendly
* UIs will present messages to the user in a bulleted or numbered list, and since
* each row only has one cell, the "table" capabilities aren't much different than one
* would get from "div"s. So this overriding renderer uses "div" instead.
*
* TODO Implement "table" and "list", and the trimmings ("detail", "title", etc.) to
* make this a full replacement.
*
* TODO Possibly add a TLD to make styling the container possible.
*
* To replace the JSF renderer with no further fuss, just paste this into faces-config.xml :
*
* <render-kit>
* <renderer>
* <component-family>javax.faces.Messages</component-family>
* <renderer-type>javax.faces.Messages</renderer-type>
* <renderer-class>org.sakaiproject.tool.section.jsf.DivMessagesRenderer</renderer-class>
* </renderer>
* </render-kit>
*
* Notes added by JH below:
*
* This messages renderer will check in two places for faces messages: in the
* faces context, and in the session-scoped messagesBean.
*
*/
public class DivMessagesRenderer extends DivMessageRendererBase {
private static final Log logger = LogFactory.getLog(DivMessagesRenderer.class);
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
if (!component.isRendered()) {
return;
}
List allMessages = combineMessages(context, component);
if (allMessages.size() == 0) {
return;
}
for(Iterator messages = allMessages.iterator(); messages.hasNext();) {
FacesMessage message = (FacesMessage)messages.next();
renderMessage(context, component, message);
}
}
/**
* Combine the messages from the redirect-safe messages bean and those
* stored in the faces context. If there are messages associated with
* individual components, but no global messages, then add a global message
* pointing the user to check for component messages ('See messages below'
* for instance).
*
* @param context
* @param component
* @return
*/
private List combineMessages(FacesContext context, UIComponent component) {
List redirectSafeMessages = ((MessagingBean)JsfUtil.resolveVariable("messagingBean")).getMessagesAndClear();
List allMessages = new ArrayList(redirectSafeMessages);
boolean globalOnly = ((UIMessages)component).isGlobalOnly();
Iterator allFacesMessages = context.getMessages();
Iterator globalFacesMessages = context.getMessages(null);
Collection componentBoundMessages = getComponentBoundMessages(allFacesMessages, globalFacesMessages);
Iterator facesMessages;
if(globalOnly) {
facesMessages = globalFacesMessages;
} else {
facesMessages = allFacesMessages;
}
// If this is a global only component, and there are no global messages,
// and there are no redirect-safe messages (which are always global), and
// there are component-bound messages, then add a global message telling
// the user to look for the component-bound messages.
if(globalOnly && redirectSafeMessages.size() == 0 && !globalFacesMessages.hasNext() && componentBoundMessages.size() != 0) {
FacesMessage seeBelowMessage = new FacesMessage(FacesMessage.SEVERITY_WARN, JsfUtil.getLocalizedMessage("validation_messages_present"), null);
allMessages.add(seeBelowMessage);
}
// We've already iterated over the facesMessage iterator, so we need to get them again so we can iterate again.
// This is ugly... is there a better way to do this?
if(globalOnly) {
facesMessages = context.getMessages(null);
} else {
facesMessages = context.getMessages();
}
for(Iterator msgs = facesMessages; facesMessages.hasNext();) {
allMessages.add(facesMessages.next());
}
return allMessages;
}
/**
* Finds the non-global messages
*
* @param allFacesMessages
* @param globalFacesMessages
* @return
*/
private Collection getComponentBoundMessages(Iterator allFacesMessages, Iterator globalFacesMessages) {
List allFacesMessagesList = new ArrayList();
for(Iterator msgs = allFacesMessages; msgs.hasNext();) {
allFacesMessagesList.add(msgs.next());
}
List globalFacesMessagesList = new ArrayList();
for(Iterator msgs = globalFacesMessages; msgs.hasNext();) {
globalFacesMessagesList.add(msgs.next());
}
allFacesMessagesList.removeAll(globalFacesMessagesList);
if(logger.isDebugEnabled())logger.debug(allFacesMessagesList.size() + " component bound messages");
return allFacesMessagesList;
}
}