blob: 9e488a9b420f64669e130e1a5924d1d905455521 [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 com.opensymphony.xwork2.validator.validators;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.CompositeTextProvider;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.validator.ActionValidatorManager;
import com.opensymphony.xwork2.validator.DelegatingValidatorContext;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.ValidatorContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* <!-- START SNIPPET: javadoc -->
*
* <p>
* The VisitorFieldValidator allows you to forward validation to object
* properties of your action using the object's own validation files. This
* allows you to use the ModelDriven development pattern and manage your
* validations for your models in one place, where they belong, next to your
* model classes. The VisitorFieldValidator can handle either simple Object
* properties, Collections of Objects, or Arrays.
* </p>
*
* <!-- END SNIPPET: javadoc -->
*
* <!-- START SNIPPET: parameters -->
* <ul>
* <li>fieldName - field name if plain-validator syntax is used, not needed if field-validator syntax is used</li>
* <li>context - the context of which validation should take place. Optional</li>
* <li>appendPrefix - the prefix to be added to field. Optional </li>
* </ul>
* <!-- END SNIPPET: parameters -->
*
* <pre>
* <!-- START SNIPPET: example -->
* &lt;validators&gt;
* &lt;!-- Plain Validator Syntax --&gt;
* &lt;validator type="visitor"&gt;
* &lt;param name="fieldName"&gt;user&lt;/param&gt;
* &lt;param name="context"&gt;myContext&lt;/param&gt;
* &lt;param name="appendPrefix"&gt;true&lt;/param&gt;
* &lt;/validator&gt;
*
* &lt;!-- Field Validator Syntax --&gt;
* &lt;field name="user"&gt;
* &lt;field-validator type="visitor"&gt;
* &lt;param name="context"&gt;myContext&lt;/param&gt;
* &lt;param name="appendPrefix"&gt;true&lt;/param&gt;
* &lt;/field-validator&gt;
* &lt;/field&gt;
* &lt;/validators&gt;
* <!-- END SNIPPET: example -->
* </pre>
*
* <!-- START SNIPPET: explanation -->
* <p>In the example above, if the action's getUser() method return User object, XWork
* will look for User-myContext-validation.xml for the validators. Since appednPrefix is true,
* every field name will be prefixed with 'user' such that if the actual field name for 'name' is
* 'user.name' </p>
* <!-- END SNIPPET: explanation -->
*
* @author Jason Carreira
* @author Rainer Hermanns
*/
public class VisitorFieldValidator extends FieldValidatorSupport {
private static final Logger LOG = LogManager.getLogger(VisitorFieldValidator.class);
private String context;
private boolean appendPrefix = true;
private ActionValidatorManager actionValidatorManager;
@Inject
public void setActionValidatorManager(ActionValidatorManager mgr) {
this.actionValidatorManager = mgr;
}
/**
* @param appendPrefix whether the field name of this field validator should be prepended to the field name of
* the visited field to determine the full field name when an error occurs. The default is
* true.
*/
public void setAppendPrefix(boolean appendPrefix) {
this.appendPrefix = appendPrefix;
}
/**
* @return whether the field name of this field validator should be prepended to the field name of
* the visited field to determine the full field name when an error occurs. The default is
* true.
*/
public boolean isAppendPrefix() {
return appendPrefix;
}
public void setContext(String context) {
this.context = context;
}
public String getContext() {
return context;
}
public void validate(Object object) throws ValidationException {
String fieldName = getFieldName();
Object value = this.getFieldValue(fieldName, object);
if (value == null) {
LOG.warn("The visited object is null, VisitorValidator will not be able to handle validation properly. Please make sure the visited object is not null for VisitorValidator to function properly");
return;
}
ValueStack stack = ActionContext.getContext().getValueStack();
stack.push(object);
String visitorContext = (context == null) ? ActionContext.getContext().getActionName() : context;
if (value instanceof Collection) {
Collection coll = (Collection) value;
Object[] array = coll.toArray();
validateArrayElements(array, fieldName, visitorContext);
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
validateArrayElements(array, fieldName, visitorContext);
} else {
validateObject(fieldName, value, visitorContext);
}
stack.pop();
}
private void validateArrayElements(Object[] array, String fieldName, String visitorContext) throws ValidationException {
if (array == null) {
return;
}
for (int i = 0; i < array.length; i++) {
Object o = array[i];
if (o != null) {
validateObject(fieldName + "[" + i + "]", o, visitorContext);
}
}
}
private void validateObject(String fieldName, Object o, String visitorContext) throws ValidationException {
ValueStack stack = ActionContext.getContext().getValueStack();
stack.push(o);
ValidatorContext validatorContext;
if (appendPrefix) {
ValidatorContext parent = getValidatorContext();
validatorContext = new AppendingValidatorContext(parent, createTextProvider(o, parent), fieldName, getMessage(o));
} else {
ValidatorContext parent = getValidatorContext();
CompositeTextProvider textProvider = createTextProvider(o, parent);
validatorContext = new DelegatingValidatorContext(parent, textProvider, parent);
}
actionValidatorManager.validate(o, visitorContext, validatorContext);
stack.pop();
}
private CompositeTextProvider createTextProvider(Object o, ValidatorContext parent) {
List<TextProvider> textProviders = new LinkedList<>();
if (o instanceof TextProvider) {
textProviders.add((TextProvider) o);
} else {
textProviders.add(textProviderFactory.createInstance(o.getClass()));
}
textProviders.add(parent);
return new CompositeTextProvider(textProviders);
}
public static class AppendingValidatorContext extends DelegatingValidatorContext {
private String field;
private String message;
private ValidatorContext parent;
public AppendingValidatorContext(ValidatorContext parent, TextProvider textProvider, String field, String message) {
super(parent, textProvider, parent);
this.field = field;
this.message = message;
this.parent = parent;
}
/**
* Translates a simple field name into a full field name in Ognl syntax
*
* @param fieldName field name in OGNL syntax
* @return full field name in OGNL syntax
*/
@Override
public String getFullFieldName(String fieldName) {
if (parent instanceof VisitorFieldValidator.AppendingValidatorContext) {
return parent.getFullFieldName(field + "." + fieldName);
}
return field + "." + fieldName;
}
public String getFieldNameWithField(String fieldName) {
return field + "." + fieldName;
}
@Override
public void addActionError(String anErrorMessage) {
super.addFieldError(getFieldNameWithField(field), message + anErrorMessage);
}
@Override
public void addFieldError(String fieldName, String errorMessage) {
super.addFieldError(getFieldNameWithField(fieldName), message + errorMessage);
}
}
}