com.sun.faces.application.ValidateComponentNesting Maven / Gradle / Ivy
package com.sun.faces.application;
import java.util.EnumSet;
import javax.faces.application.FacesMessage;
import javax.faces.application.ProjectStage;
import javax.faces.component.ActionSource;
import javax.faces.component.ActionSource2;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIViewAction;
import javax.faces.component.UIViewParameter;
import javax.faces.component.UIViewRoot;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import com.sun.faces.util.MessageUtils;
/**
* ValidateFormComponentNesting performs component tree validation to assure {@link ActionSource}
* , {@link ActionSource2} and {@link EditableValueHolder} components are placed inside a form.
* ValidateFormComponentNesting is installed automatically if {@link ProjectStage#Development} is
* active.
*
* @author dueni
*
*/
public class ValidateComponentNesting implements SystemEventListener {
@Override
public boolean isListenerForSource(Object source) {
return (source instanceof UIViewRoot);
}
/**
* Process PostAddToViewEvent on UIViewRoot to validate form - action/input nesting.
*/
@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIComponent root = (UIComponent) event.getSource();
FacesContext jsf = FacesContext.getCurrentInstance();
EnumSet hints = EnumSet.of(VisitHint.SKIP_ITERATION);
VisitContext visitContext = VisitContext.createVisitContext(jsf, null, hints);
root.visitTree(visitContext, new ValidateFormNestingCallback());
}
static class ValidateFormNestingCallback implements VisitCallback {
// report missing form problem only once
boolean reportedOmittedFormOnce = false;
// report missing metadata problem only once
boolean reportedOmittedMetadataOnce = false;
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
// default result: continue tree walk
VisitResult result = VisitResult.ACCEPT;
if (target instanceof UIForm || target.getFamily().endsWith("Form")
|| UIViewRoot.METADATA_FACET_NAME.equals(target.getId())) {
// stop tree walk if component is of type UIForm or component family ends on "Form"
// or if the component is the UIPanel with id METADATA_FACET_NAME
result = VisitResult.REJECT;
} else if (target instanceof UIViewParameter || target instanceof UIViewAction) {
if (reportedOmittedMetadataOnce) {
// report first detected problem only, then stop tree walk
result = VisitResult.COMPLETE;
}
addOmittedMessage(context.getFacesContext(), target.getClientId(context
.getFacesContext()), MessageUtils.MISSING_METADATA_ERROR);
reportedOmittedMetadataOnce = true;
} else if (target instanceof EditableValueHolder || target instanceof ActionSource
|| target instanceof ActionSource2) {
if (reportedOmittedFormOnce) {
// report first detected problem only, then stop tree walk
result = VisitResult.COMPLETE;
}
// if we find ActionSource, ActionSource2 or EditableValueHolder, that component
// must be outside of a form add warning message
addOmittedMessage(context.getFacesContext(), target.getClientId(context
.getFacesContext()), MessageUtils.MISSING_FORM_ERROR);
reportedOmittedFormOnce = true;
}
return result;
}
}
/**
* method for adding a message regarding missing ancestor to context
*
* @param ctx
* @param clientId
*/
private static void addOmittedMessage(FacesContext jsf, String clientId, String key) {
Object[] params = new Object[] {};
FacesMessage m = MessageUtils.getExceptionMessage(key, params);
m.setSeverity(FacesMessage.SEVERITY_WARN);
jsf.addMessage(clientId, m);
}
}