blob: a9145d0caa2824d3712b2061e4f55becd04a6aa5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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.apache.org/licenses/LICENSE-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.apache.myfaces.view.facelets.tag.jsf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.el.ValueExpression;
import javax.faces.FacesWrapper;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.view.EditableValueHolderAttachedObjectHandler;
import javax.faces.view.facelets.ComponentHandler;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.MetaRuleset;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagException;
import javax.faces.view.facelets.TagHandlerDelegate;
import javax.faces.view.facelets.ValidatorHandler;
import org.apache.myfaces.config.MyfacesConfig;
import org.apache.myfaces.renderkit.html.util.JSFAttr;
import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.compiler.FaceletsCompilerUtils;
import org.apache.myfaces.view.facelets.tag.MetaRulesetImpl;
/**
* Handles setting a Validator instance on a EditableValueHolder. Will wire all attributes set to the Validator instance
* created/fetched. Uses the "binding" attribute for grabbing instances to apply attributes to. <p> Will only
* set/create Validator is the passed UIComponent's parent is null, signifying that it wasn't restored from an existing
* tree.</p>
*
* @author Leonardo Uribe (latest modification by $Author$)
* @version $Revision$ $Date$
*
* @since 2.0
*/
public class ValidatorTagHandlerDelegate extends TagHandlerDelegate
implements EditableValueHolderAttachedObjectHandler, FacesWrapper<ValidatorHandler>
{
/**
* if &lt;f:validateBean&gt; has no children and its disabled attribute is true,
* its validatorId will be added to the exclusion list stored under
* this key on the parent UIComponent.
*/
public final static String VALIDATOR_ID_EXCLUSION_LIST_KEY
= "org.apache.myfaces.validator.VALIDATOR_ID_EXCLUSION_LIST";
private final static String BEAN_BEFORE_JSF_PROPERTY = "oam.beanBeforeJsf";
private ValidatorHandler _delegate;
/**
* true - this tag has children
* false - this tag is a leave
*/
private final boolean _wrapMode;
private Boolean _beanBeforeJsfValidation;
public ValidatorTagHandlerDelegate(ValidatorHandler delegate)
{
_delegate = delegate;
// According to jsf 2.0 spec section 10.4.1.4
// this tag can be used as a leave within an EditableValueHolder
// or as a container to provide validator information for all
// EditableValueHolder-children (and grandchildren and ...)
// (this behavior is analog to <f:ajax>)
// --> Determine if we have children:
_wrapMode = FaceletsCompilerUtils.hasChildren(_delegate.getValidatorConfig());
_beanBeforeJsfValidation = null;
}
@Override
public void apply(FaceletContext ctx, UIComponent parent) throws IOException
{
// we need methods from AbstractFaceletContext
FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
if (_wrapMode)
{
// the tag has children --> provide validator information for all children
// FIXME the spec says we should save the validation groups in an attribute
// on the parent UIComponent, but this will be a problem in the following scenario:
// <h:form>
// <f:validateBean>
// <h:inputText />
// </f:validateBean>
// <h:inputText />
// </h:form>
// because the validator would also be applied to the second h:inputText,
// which it should not, on my opinion. In addition, mojarra also does not
// attach the validator to the second h:inputText in this scenario (blackbox test).
// So I use the same way as f:ajax for this problem. -=Jakob Korherr=-
String validatorId = _delegate.getValidatorConfig().getValidatorId();
try
{
mctx.pushEnclosingValidatorIdToStack(validatorId, this);
_delegate.applyNextHandler(ctx, parent);
}
finally
{
mctx.popEnclosingValidatorIdToStack();
}
}
else
{
// Apply only if we are creating a new component
if (!ComponentHandler.isNew(parent))
{
return;
}
// the tag is a leave --> attach validator to parent
if (parent instanceof EditableValueHolder)
{
applyAttachedObject(ctx.getFacesContext(), parent);
}
else if (UIComponent.isCompositeComponent(parent))
{
if (getFor() == null)
{
throw new TagException(_delegate.getTag(), "is nested inside a composite component"
+ " but does not have a for attribute.");
}
mctx.addAttachedObjectHandler(parent, _delegate);
}
else
{
throw new TagException(_delegate.getTag(),
"Parent not composite component or an instance of EditableValueHolder: " + parent);
}
}
}
/**
* Template method for creating a Validator instance
*
* @param ctx FaceletContext to use
* @return a new Validator instance
*/
protected Validator createValidator(FaceletContext ctx)
{
if (_delegate.getValidatorId(ctx) == null)
{
throw new TagException(_delegate.getTag(), "Default behavior invoked of requiring " +
"a validator-id passed in the constructor, must override ValidateHandler(ValidatorConfig)");
}
return ctx.getFacesContext().getApplication().createValidator(_delegate.getValidatorId(ctx));
}
@Override
public MetaRuleset createMetaRuleset(Class type)
{
MetaRuleset metaRuleset = new MetaRulesetImpl(_delegate.getTag(), type);
// ignore binding and disabled, because they are handled by DelegatingMetaTagHandler
metaRuleset.ignore(JSFAttr.BINDING_ATTR).ignore(JSFAttr.DISABLED_ATTR);
// ignore for, because it is handled by FaceletsAttachedObjectHandler
metaRuleset.ignore(JSFAttr.FOR_ATTR);
return metaRuleset;
}
@SuppressWarnings("unchecked")
@Override
public void applyAttachedObject(FacesContext context, UIComponent parent)
{
// Retrieve the current FaceletContext from FacesContext object
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(
FaceletContext.FACELET_CONTEXT_KEY);
// spec: if the disabled attribute is true, the validator should not be added.
// in addition, the validatorId, if present, should be added to an exclusion
// list on the parent component to prevent a default validator with the same
// id from beeing registered on the component.
if (_delegate.isDisabled(faceletContext))
{
// tag is disabled --> add its validatorId to the parent's exclusion list
String validatorId = _delegate.getValidatorConfig().getValidatorId();
if (validatorId != null && !validatorId.isEmpty())
{
List<String> exclusionList = (List<String>) parent.getAttributes()
.get(VALIDATOR_ID_EXCLUSION_LIST_KEY);
if (exclusionList == null)
{
exclusionList = new ArrayList<String>();
parent.getAttributes().put(VALIDATOR_ID_EXCLUSION_LIST_KEY, exclusionList);
}
exclusionList.add(validatorId);
}
}
else
{
// tag is enabled --> create the validator and attach it
// cast to a ValueHolder
EditableValueHolder evh = (EditableValueHolder) parent;
ValueExpression ve = null;
Validator v = null;
if (_delegate.getBinding() != null)
{
ve = _delegate.getBinding().getValueExpression(faceletContext, Validator.class);
v = (Validator) ve.getValue(faceletContext);
}
if (v == null)
{
v = this.createValidator(faceletContext);
if (ve != null)
{
ve.setValue(faceletContext, v);
}
}
if (v == null)
{
throw new TagException(_delegate.getTag(), "No Validator was created");
}
_delegate.setAttributes(faceletContext, v);
if (shouldBeanBeforeJsfValidationEnabled(context))
{
parent.getAttributes().put(BEAN_BEFORE_JSF_PROPERTY, Boolean.TRUE);
}
evh.addValidator(v);
}
}
private boolean shouldBeanBeforeJsfValidationEnabled(FacesContext context)
{
if (_beanBeforeJsfValidation == null)
{
_beanBeforeJsfValidation = MyfacesConfig.getCurrentInstance(context).isBeanBeforeJsfValidation();
}
return _beanBeforeJsfValidation;
}
@Override
public String getFor()
{
TagAttribute forAttribute = _delegate.getTagAttribute("for");
if (forAttribute == null)
{
return null;
}
else
{
return forAttribute.getValue();
}
}
public String getValidationGroups(FaceletContext ctx)
{
TagAttribute attribute = _delegate.getTagAttribute("validationGroups");
if (attribute == null)
{
return null;
}
else
{
return attribute.getValue(ctx);
}
}
@Override
public ValidatorHandler getWrapped()
{
return _delegate;
}
}