org.richfaces.component.behavior.ClientValidatorImpl Maven / Gradle / Ivy
/*
* $Id$
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.component.behavior;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.ActionSource;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIMessage;
import javax.faces.component.UIMessages;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.convert.Converter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.BehaviorEvent;
import javax.faces.render.ClientBehaviorRenderer;
import javax.faces.render.RenderKit;
import javax.faces.validator.BeanValidator;
import javax.faces.validator.Validator;
import org.ajax4jsf.component.behavior.AjaxBehavior;
import org.ajax4jsf.javascript.ScriptUtils;
import org.richfaces.application.ServiceTracker;
import org.richfaces.cdk.annotations.Attribute;
import org.richfaces.cdk.annotations.JsfBehavior;
import org.richfaces.cdk.annotations.Tag;
import org.richfaces.cdk.annotations.TagType;
import org.richfaces.component.ClientSideMessage;
import org.richfaces.javascript.JavaScriptService;
import org.richfaces.javascript.Message;
import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
import org.richfaces.renderkit.html.ClientValidatorRenderer;
import org.richfaces.renderkit.html.FormClientValidatorRenderer;
import org.richfaces.validator.BeanValidatorService;
import org.richfaces.validator.ConverterDescriptor;
import org.richfaces.validator.FacesBeanValidator;
import org.richfaces.validator.FacesConverterService;
import org.richfaces.validator.FacesValidatorService;
import org.richfaces.validator.ValidatorDescriptor;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
/**
* The <rich:validator> behavior adds client-side validation to a form input control based on registered server-side validators. It provides this validation without the need to reproduce the server-side annotations.
*
* The <rich:validator> behavior triggers all client validator annotations listed in the relevant managed bean.
*
* @author [email protected]
*/
@JsfBehavior(id = "org.richfaces.behavior.ClientValidator", tag = @Tag(name = "validator", handler = "org.richfaces.view.facelets.html.ClientValidatorHandler", type = TagType.Facelets), attributes = {
"validator-props.xml", "immediate-prop.xml" })
public class ClientValidatorImpl extends AjaxBehavior implements ClientValidatorBehavior {
private static final Set NONE = Collections.emptySet();
private static final Set THIS = Collections.singleton("@this");
private static final Class>[] EMPTY_GROUPS = new Class>[0];
private static final String VALUE = "value";
private static final Logger LOG = RichfacesLogger.COMPONENTS.getLogger();
private Class>[] groups;
private static final Function super FacesMessage, Message> MESSAGES_TRANSFORMER = new Function() {
public Message apply(FacesMessage msg) {
return new Message(msg);
}
};
protected enum Properties {
onvalid,
oninvalid
}
@Override
public String getScript(ClientBehaviorContext behaviorContext) {
if (behaviorContext.getComponent() instanceof EditableValueHolder) {
return super.getScript(behaviorContext);
} else if (behaviorContext.getComponent() instanceof ActionSource) {
ClientBehaviorRenderer renderer = getRenderer(behaviorContext.getFacesContext(),
FormClientValidatorRenderer.RENDERER_TYPE);
return renderer.getScript(behaviorContext, this);
} else {
throw new FacesException("Invalid target for client-side validator behavior");
}
}
@Override
public String getRendererType() {
return ClientValidatorRenderer.RENDERER_TYPE;
}
@Override
public void broadcast(BehaviorEvent event) throws AbortProcessingException {
// Add message components to re-render list ( if any )
FacesContext facesContext = FacesContext.getCurrentInstance();
PartialViewContext partialViewContext = facesContext.getPartialViewContext();
if (partialViewContext.isAjaxRequest()) {
UIComponent component = event.getComponent();
if (component instanceof EditableValueHolder) {
String clientId = component.getClientId(facesContext);
final ImmutableList messages = ImmutableList.copyOf(Iterators.transform(facesContext.getMessages(clientId), MESSAGES_TRANSFORMER));
JavaScriptService javaScriptService = ServiceTracker.getService(JavaScriptService.class);
javaScriptService.addPageReadyScript(facesContext, new MessageUpdateScript(clientId, messages));
if (messages.isEmpty()) {
final String onvalid = getOnvalid();
if (onvalid != null && !"".equals(onvalid.trim())) {
javaScriptService.addPageReadyScript(facesContext, new AnonymousFunctionCall().addToBody(onvalid));
}
} else {
final String oninvalid = getOninvalid();
if (oninvalid != null && !"".equals(oninvalid.trim())) {
javaScriptService.addPageReadyScript(facesContext, new AnonymousFunctionCall("messages").addParameterValue(ScriptUtils.toScript(messages)).addToBody(oninvalid));
}
}
}
}
super.broadcast(event);
}
@Override
public void setLiteralAttribute(String name, Object value) {
super.setLiteralAttribute(name, value);
if (compare(Properties.oninvalid, name)) {
setOninvalid((String) value);
} else if (compare(Properties.onvalid, name)) {
setOnvalid((String) value);
}
}
public Set getMessages(FacesContext context, UIComponent component) {
Set messages = new HashSet();
findMessages(component.getParent(), component, messages, false, component.getId());
// TODO - enable then UIRichMessages will be done
// findRichMessages(context, context.getViewRoot(), messages);
return messages;
}
/**
* Find all instances of the {@link org.richfaces.component.UIRichMessages} and update list of the rendered messages.
*
* @param context
* @param component
* @param messages
*/
protected void findRichMessages(FacesContext context, UIComponent component, String id, Set messages) {
Iterator facetsAndChildren = component.getFacetsAndChildren();
while (facetsAndChildren.hasNext()) {
UIComponent child = (UIComponent) facetsAndChildren.next();
if (child instanceof ClientSideMessage) {
ClientSideMessage richMessage = (ClientSideMessage) child;
if (null == richMessage.getFor()) {
richMessage.updateMessages(context, id);
messages.add(child);
}
} else {
findRichMessages(context, child, id, messages);
}
}
}
/**
* Recursive search messages for the parent component.
*
* @param parent
* @param component
* @param messages
* @param id
*/
protected boolean findMessages(UIComponent parent, UIComponent component, Set messages, boolean found,
Object id) {
Iterator facetsAndChildren = parent.getFacetsAndChildren();
while (facetsAndChildren.hasNext()) {
UIComponent child = (UIComponent) facetsAndChildren.next();
if (child != component) {
if (child instanceof UIMessage || child instanceof UIMessages) {
UIComponent message = (UIComponent) child;
Object targetId = message.getAttributes().get("for");
if (null != targetId && targetId.equals(id)) {
messages.add(message);
found = true;
}
} else {
found |= findMessages(child, null, messages, found, id);
}
}
}
if (!(found && parent instanceof NamingContainer) && component != null) {
UIComponent newParent = parent.getParent();
if (null != newParent) {
found = findMessages(newParent, parent, messages, found, id);
}
}
return found;
}
/**
*
* Look up for {@link ClientBehaviorRenderer} instence
*
*
* @param context current JSF context
* @param rendererType desired renderer type
* @return renderer instance
* @throws {@link FacesException} if renderer can not be found
*/
protected ClientBehaviorRenderer getRenderer(FacesContext context, String rendererType) {
if (null == context || null == rendererType) {
throw new NullPointerException();
}
ClientBehaviorRenderer renderer = null;
RenderKit renderKit = context.getRenderKit();
if (null != renderKit) {
renderer = renderKit.getClientBehaviorRenderer(rendererType);
if (null == renderer) {
throw new FacesException("No ClientBehaviorRenderer found for type " + rendererType);
}
} else {
throw new FacesException("No renderkit available");
}
return renderer;
}
/*
* (non-Javadoc)
*
* @see org.richfaces.component.behavior.ClientValidatorBehavior#getConverter(javax.faces.component.behavior.
* ClientBehaviorContext)
*/
public ConverterDescriptor getConverter(ClientBehaviorContext context) throws ConverterNotFoundException {
UIComponent component = context.getComponent();
if (component instanceof EditableValueHolder) {
EditableValueHolder input = (EditableValueHolder) component;
FacesContext facesContext = context.getFacesContext();
Converter converter = input.getConverter();
if (null == converter) {
Class> valueType;
ValueExpression valueExpression = component.getValueExpression(VALUE);
if (null != valueExpression) {
valueType = valueExpression.getType(facesContext.getELContext());
converter = createConverterByType(facesContext, valueType);
}
}
if (null != converter) {
FacesConverterService converterService = ServiceTracker.getService(facesContext, FacesConverterService.class);
String converterMessage = (String) component.getAttributes().get("converterMessage");
return converterService.getConverterDescription(facesContext, input, converter, converterMessage);
} else {
return null;
}
} else {
throw new ConverterNotFoundException("Component does not implement EditableValueHolder" + component);
}
}
Converter createConverterByType(FacesContext facesContext, Class> valueType) throws ConverterNotFoundException {
Converter converter = null;
if (valueType != null && valueType != Object.class) {
Application application = facesContext.getApplication();
converter = application.createConverter(valueType);
if (null == converter && valueType != String.class) {
throw new ConverterNotFoundException("No converter registered for type " + valueType.getName());
}
}
return converter;
}
/*
* (non-Javadoc)
*
* @see org.richfaces.component.behavior.ClientValidatorBehavior#getValidators(javax.faces.component.behavior.
* ClientBehaviorContext)
*/
public Collection getValidators(ClientBehaviorContext context) {
UIComponent component = context.getComponent();
if (component instanceof EditableValueHolder) {
Collection validators = Lists.newArrayList();
EditableValueHolder input = (EditableValueHolder) component;
Validator[] facesValidators = input.getValidators();
FacesContext facesContext = context.getFacesContext();
if (facesValidators.length > 0) {
String validatorMessage = (String) component.getAttributes().get("validatorMessage");
boolean beanValidatorsProcessed = false;
FacesValidatorService facesValidatorService = ServiceTracker.getService(facesContext,
FacesValidatorService.class);
for (Validator validator : facesValidators) {
if (validator instanceof BeanValidator || validator instanceof FacesBeanValidator) {
ValueExpression valueExpression = component.getValueExpression(VALUE);
if (null != valueExpression && !beanValidatorsProcessed) {
BeanValidatorService beanValidatorService = ServiceTracker.getService(facesContext,
BeanValidatorService.class);
validators.addAll(beanValidatorService.getConstrains(facesContext, valueExpression,
validatorMessage, getGroups()));
beanValidatorsProcessed = true;
}
} else {
validators.add(facesValidatorService.getValidatorDescription(facesContext, input, validator,
validatorMessage));
}
}
}
return validators;
} else {
throw new FacesException("Component " + component.getClass().getName()
+ " does not implement EditableValueHolder interface");
}
}
public Class>[] getGroups() {
if (groups != null) {
return groups;
}
ValueExpression expression = getValueExpression("groups");
if (expression != null) {
FacesContext ctx = FacesContext.getCurrentInstance();
return (Class>[]) expression.getValue(ctx.getELContext());
}
return EMPTY_GROUPS;
}
public void setGroups(Class>... groups) {
this.groups = groups;
clearInitialState();
}
public String getAjaxScript(ClientBehaviorContext context) {
return getRenderer(context.getFacesContext(), BEHAVIOR_ID).getScript(context, this);
}
@Override
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
Object[] values;
Object superState = super.saveState(context);
if (initialStateMarked()) {
if (superState == null) {
values = null;
} else {
values = new Object[] { superState };
}
} else {
values = new Object[2];
values[0] = superState;
values[1] = groups;
}
return values;
}
@Override
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state != null) {
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
if (values.length != 1) {
groups = (Class>[]) values[1];
// If we saved state last time, save state again next time.
clearInitialState();
}
}
}
/*
* (non-Javadoc)
*
* @see org.richfaces.component.behavior.ClientValidatorBehavior#isImmediateSet()
*/
public boolean isImmediateSet() {
// TODO - implement this method in RichFaces AjaxBehavior
return false;
}
// Disable processing for any component except validated input.
@Override
public boolean isLimitRender() {
return true;
}
@Override
public boolean isBypassUpdates() {
return true;
}
@Override
public Collection getExecute() {
return THIS;
}
@Override
public Collection getRender() {
return NONE;
}
@Attribute
public String getOnvalid() {
return (String) getStateHelper().eval(Properties.onvalid);
}
public void setOnvalid(String value) {
getStateHelper().put(Properties.onvalid, value);
}
@Attribute
public String getOninvalid() {
return (String) getStateHelper().eval(Properties.oninvalid);
}
public void setOninvalid(String value) {
getStateHelper().put(Properties.oninvalid, value);
}
}