mirror of
https://github.com/apache/struts.git
synced 2026-08-07 15:46:57 +00:00
Merge pull request #659 from atlassian/WW-5284-clean-up-validator
WW-5284 Refactor ActionValidatorManager implementations
This commit is contained in:
-289
@@ -1,289 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.opensymphony.xwork2.FileManager;
|
||||
import com.opensymphony.xwork2.FileManagerFactory;
|
||||
import com.opensymphony.xwork2.TextProviderFactory;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static java.util.Collections.synchronizedMap;
|
||||
|
||||
public abstract class AbstractActionValidatorManager implements ActionValidatorManager {
|
||||
|
||||
/**
|
||||
* The file suffix for any validation file.
|
||||
*/
|
||||
protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
|
||||
|
||||
protected final Map<String, List<ValidatorConfig>> validatorCache = synchronizedMap(new HashMap<>());
|
||||
protected final Map<String, List<ValidatorConfig>> validatorFileCache = synchronizedMap(new HashMap<>());
|
||||
private static final Logger LOG = LogManager.getLogger(AbstractActionValidatorManager.class);
|
||||
|
||||
protected ValidatorFactory validatorFactory;
|
||||
protected ValidatorFileParser validatorFileParser;
|
||||
protected FileManager fileManager;
|
||||
protected boolean reloadingConfigs;
|
||||
protected TextProviderFactory textProviderFactory;
|
||||
|
||||
@Inject
|
||||
public void setValidatorFactory(ValidatorFactory fac) {
|
||||
this.validatorFactory = fac;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setValidatorFileParser(ValidatorFileParser parser) {
|
||||
this.validatorFileParser = parser;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
|
||||
this.fileManager = fileManagerFactory.getFileManager();
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, required = false)
|
||||
public void setReloadingConfigs(String reloadingConfigs) {
|
||||
this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setTextProviderFactory(TextProviderFactory textProviderFactory) {
|
||||
this.textProviderFactory = textProviderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context) throws ValidationException {
|
||||
validate(object, context, (String) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context, String method) throws ValidationException {
|
||||
ValidatorContext validatorContext = new DelegatingValidatorContext(object, textProviderFactory);
|
||||
validate(object, context, validatorContext, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
|
||||
validate(object, context, validatorContext, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
|
||||
List<Validator> validators = getValidators(object.getClass(), context, method);
|
||||
Set<String> shortcircuitedFields = null;
|
||||
|
||||
for (Validator validator : validators) {
|
||||
try {
|
||||
validator.setValidatorContext(validatorContext);
|
||||
|
||||
LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
|
||||
|
||||
FieldValidator fValidator = null;
|
||||
String fullFieldName = null;
|
||||
|
||||
if (validator instanceof FieldValidator) {
|
||||
fValidator = (FieldValidator) validator;
|
||||
fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());
|
||||
|
||||
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
|
||||
LOG.debug("Short-circuited, skipping");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
|
||||
// get number of existing errors
|
||||
List<String> errs = null;
|
||||
|
||||
if (fValidator != null) {
|
||||
if (validatorContext.hasFieldErrors()) {
|
||||
Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
|
||||
|
||||
if (fieldErrors != null) {
|
||||
errs = new ArrayList<>(fieldErrors);
|
||||
}
|
||||
}
|
||||
} else if (validatorContext.hasActionErrors()) {
|
||||
Collection<String> actionErrors = validatorContext.getActionErrors();
|
||||
|
||||
if (actionErrors != null) {
|
||||
errs = new ArrayList<>(actionErrors);
|
||||
}
|
||||
}
|
||||
|
||||
validator.validate(object);
|
||||
|
||||
if (fValidator != null) {
|
||||
if (validatorContext.hasFieldErrors()) {
|
||||
Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
|
||||
|
||||
if ((errCol != null) && !errCol.equals(errs)) {
|
||||
LOG.debug("Short-circuiting on field validation");
|
||||
|
||||
if (shortcircuitedFields == null) {
|
||||
shortcircuitedFields = new TreeSet<>();
|
||||
}
|
||||
|
||||
shortcircuitedFields.add(fullFieldName);
|
||||
}
|
||||
}
|
||||
} else if (validatorContext.hasActionErrors()) {
|
||||
Collection<String> errCol = validatorContext.getActionErrors();
|
||||
|
||||
if ((errCol != null) && !errCol.equals(errs)) {
|
||||
LOG.debug("Short-circuiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
validator.validate(object);
|
||||
} finally {
|
||||
validator.setValidatorContext(null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This method 'collects' all the validator configurations for a given
|
||||
* action invocation.</p>
|
||||
*
|
||||
* <p>It will traverse up the class hierarchy looking for validators for every super class
|
||||
* and directly implemented interface of the current action, as well as adding validators for
|
||||
* any alias of this invocation. Nifty!</p>
|
||||
*
|
||||
* <p>Given the following class structure:</p>
|
||||
* <pre>
|
||||
* interface Thing;
|
||||
* interface Animal extends Thing;
|
||||
* interface Quadraped extends Animal;
|
||||
* class AnimalImpl implements Animal;
|
||||
* class QuadrapedImpl extends AnimalImpl implements Quadraped;
|
||||
* class Dog extends QuadrapedImpl;
|
||||
* </pre>
|
||||
*
|
||||
* <p>This method will look for the following config files for Dog:</p>
|
||||
* <pre>
|
||||
* Animal
|
||||
* Animal-context
|
||||
* AnimalImpl
|
||||
* AnimalImpl-context
|
||||
* Quadraped
|
||||
* Quadraped-context
|
||||
* QuadrapedImpl
|
||||
* QuadrapedImpl-context
|
||||
* Dog
|
||||
* Dog-context
|
||||
* </pre>
|
||||
*
|
||||
* <p>Note that the validation rules for Thing is never looked for because no class in the
|
||||
* hierarchy directly implements Thing.</p>
|
||||
*
|
||||
* @param clazz the Class to look up validators for.
|
||||
* @param context the context to use when looking up validators.
|
||||
* @param checkFile true if the validation config file should be checked to see if it has been
|
||||
* updated.
|
||||
* @param checked the set of previously checked class-contexts, null if none have been checked
|
||||
* @return a list of validator configs for the given class and context.
|
||||
*/
|
||||
protected List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
|
||||
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
|
||||
|
||||
if (checked == null) {
|
||||
checked = new TreeSet<>();
|
||||
} else if (checked.contains(clazz.getName())) {
|
||||
return validatorConfigs;
|
||||
}
|
||||
|
||||
if (clazz.isInterface()) {
|
||||
for (Class anInterface : clazz.getInterfaces()) {
|
||||
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
|
||||
}
|
||||
} else {
|
||||
if (!clazz.equals(Object.class)) {
|
||||
validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
|
||||
}
|
||||
}
|
||||
|
||||
// look for validators for implemented interfaces
|
||||
for (Class anInterface1 : clazz.getInterfaces()) {
|
||||
if (checked.contains(anInterface1.getName())) {
|
||||
continue;
|
||||
}
|
||||
validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
|
||||
if (context != null) {
|
||||
validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
|
||||
}
|
||||
checked.add(anInterface1.getName());
|
||||
}
|
||||
|
||||
validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
|
||||
if (context != null) {
|
||||
validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
|
||||
}
|
||||
checked.add(clazz.getName());
|
||||
|
||||
return validatorConfigs;
|
||||
}
|
||||
|
||||
protected abstract List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile);
|
||||
|
||||
protected abstract List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile);
|
||||
|
||||
protected List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
|
||||
List<ValidatorConfig> retList = Collections.emptyList();
|
||||
|
||||
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
|
||||
|
||||
if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
|
||||
try (InputStream is = fileManager.loadFile(fileUrl)) {
|
||||
if (is != null) {
|
||||
retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Caught exception while closing file {}", fileName, e);
|
||||
}
|
||||
|
||||
validatorFileCache.put(fileName, retList);
|
||||
} else {
|
||||
retList = validatorFileCache.get(fileName);
|
||||
}
|
||||
|
||||
return retList;
|
||||
}
|
||||
}
|
||||
+6
-66
@@ -22,10 +22,7 @@ import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ActionProxy;
|
||||
import com.opensymphony.xwork2.config.entities.ActionConfig;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -37,56 +34,9 @@ import java.util.List;
|
||||
* @author Rainer Hermanns
|
||||
* @author jepjep
|
||||
*/
|
||||
public class AnnotationActionValidatorManager extends AbstractActionValidatorManager {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(AnnotationActionValidatorManager.class);
|
||||
public class AnnotationActionValidatorManager extends DefaultActionValidatorManager {
|
||||
|
||||
@Override
|
||||
public List<Validator> getValidators(Class clazz, String context) {
|
||||
return getValidators(clazz, context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Validator> getValidators(Class clazz, String context, String method) {
|
||||
String validatorKey = buildValidatorKey(clazz, context);
|
||||
|
||||
if (validatorCache.containsKey(validatorKey)) {
|
||||
if (reloadingConfigs) {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
|
||||
}
|
||||
} else {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
|
||||
}
|
||||
|
||||
// get the set of validator configs
|
||||
List<ValidatorConfig> cfgs = new ArrayList<>(validatorCache.get(validatorKey));
|
||||
|
||||
ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
|
||||
// create clean instances of the validators for the caller's use
|
||||
ArrayList<Validator> validators = new ArrayList<>(cfgs.size());
|
||||
for (ValidatorConfig cfg : cfgs) {
|
||||
if (method == null || method.equals(cfg.getParams().get("methodName"))) {
|
||||
Validator validator = validatorFactory.getValidator(
|
||||
new ValidatorConfig.Builder(cfg)
|
||||
.removeParam("methodName")
|
||||
.build());
|
||||
validator.setValidatorType(cfg.getType());
|
||||
validator.setValueStack(stack);
|
||||
validators.add(validator);
|
||||
}
|
||||
}
|
||||
|
||||
return validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a key for validators - used when caching validators.
|
||||
*
|
||||
* @param clazz the action.
|
||||
* @param context context
|
||||
* @return a validator key which is the class name plus context.
|
||||
*/
|
||||
protected String buildValidatorKey(Class clazz, String context) {
|
||||
ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
|
||||
ActionProxy proxy = invocation.getProxy();
|
||||
@@ -98,20 +48,11 @@ public class AnnotationActionValidatorManager extends AbstractActionValidatorMan
|
||||
sb.append(config.getPackageName());
|
||||
sb.append("/");
|
||||
}
|
||||
|
||||
// the key needs to use the name of the action from the config file,
|
||||
// instead of the url, so wild card actions will have the same validator
|
||||
// see WW-2996
|
||||
|
||||
// UPDATE:
|
||||
// WW-3753 Using the config name instead of the context only for
|
||||
// wild card actions to keep the flexibility provided
|
||||
// by the original design (such as mapping different contexts
|
||||
// to the same action and method if desired)
|
||||
|
||||
// UPDATE:
|
||||
// WW-4536 Using NameVariablePatternMatcher allows defines actions
|
||||
// with patterns enclosed with '{}', it's similar case to WW-3753
|
||||
// WW-2996: key needs to use the name of the action from the config file, instead of the url,
|
||||
// so wildcard actions will have the same validator
|
||||
// WW-3753: Using the config name instead of the context only for wildcard actions to keep the flexibility
|
||||
// provided by the original design (such as mapping different contexts to the same action and method if desired)
|
||||
// WW-4536: Using NamedVariablePatternMatcher allows defines actions with patterns enclosed with '{}'
|
||||
String configName = config.getName();
|
||||
if (configName.contains(ActionConfig.WILDCARD) || (configName.contains("{") && configName.contains("}"))) {
|
||||
sb.append(configName);
|
||||
@@ -120,7 +61,6 @@ public class AnnotationActionValidatorManager extends AbstractActionValidatorMan
|
||||
} else {
|
||||
sb.append(context);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
+275
-30
@@ -19,12 +19,29 @@
|
||||
package com.opensymphony.xwork2.validator;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.FileManager;
|
||||
import com.opensymphony.xwork2.FileManagerFactory;
|
||||
import com.opensymphony.xwork2.TextProviderFactory;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static java.util.Collections.synchronizedMap;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -43,9 +60,102 @@ import java.util.List;
|
||||
* @author James House
|
||||
* @author Rainer Hermanns
|
||||
*/
|
||||
public class DefaultActionValidatorManager extends AbstractActionValidatorManager {
|
||||
public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
|
||||
private final static Logger LOG = LogManager.getLogger(DefaultActionValidatorManager.class);
|
||||
/**
|
||||
* The file suffix for any validation file.
|
||||
*/
|
||||
protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
|
||||
|
||||
protected final Map<String, List<ValidatorConfig>> validatorCache = synchronizedMap(new HashMap<>());
|
||||
protected final Map<String, List<ValidatorConfig>> validatorFileCache = synchronizedMap(new HashMap<>());
|
||||
private static final Logger LOG = LogManager.getLogger(DefaultActionValidatorManager.class);
|
||||
|
||||
protected ValidatorFactory validatorFactory;
|
||||
protected ValidatorFileParser validatorFileParser;
|
||||
protected FileManager fileManager;
|
||||
protected boolean reloadingConfigs;
|
||||
protected TextProviderFactory textProviderFactory;
|
||||
|
||||
@Inject
|
||||
public void setValidatorFactory(ValidatorFactory fac) {
|
||||
this.validatorFactory = fac;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setValidatorFileParser(ValidatorFileParser parser) {
|
||||
this.validatorFileParser = parser;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
|
||||
this.fileManager = fileManagerFactory.getFileManager();
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, required = false)
|
||||
public void setReloadingConfigs(String reloadingConfigs) {
|
||||
this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setTextProviderFactory(TextProviderFactory textProviderFactory) {
|
||||
this.textProviderFactory = textProviderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context) throws ValidationException {
|
||||
validate(object, context, (String) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context, String method) throws ValidationException {
|
||||
ValidatorContext validatorContext = new DelegatingValidatorContext(object, textProviderFactory);
|
||||
validate(object, context, validatorContext, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
|
||||
validate(object, context, validatorContext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a key for validators - used when caching validators.
|
||||
*
|
||||
* @param clazz the action.
|
||||
* @param context context
|
||||
* @return a validator key which is the class name plus context.
|
||||
*/
|
||||
protected String buildValidatorKey(Class clazz, String context) {
|
||||
return clazz.getName() + "/" + context;
|
||||
}
|
||||
|
||||
protected Validator getValidatorFromValidatorConfig(ValidatorConfig config, ValueStack stack) {
|
||||
Validator validator = validatorFactory.getValidator(config);
|
||||
validator.setValidatorType(config.getType());
|
||||
validator.setValueStack(stack);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
|
||||
String validatorKey = buildValidatorKey(clazz, context);
|
||||
|
||||
if (!validatorCache.containsKey(validatorKey)) {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
|
||||
} else if (reloadingConfigs) {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
|
||||
}
|
||||
|
||||
ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
List<ValidatorConfig> configs = validatorCache.get(validatorKey);
|
||||
List<Validator> validators = new ArrayList<>();
|
||||
for (ValidatorConfig config : configs) {
|
||||
if (method == null || method.equals(config.getParams().get("methodName"))) {
|
||||
validators.add(getValidatorFromValidatorConfig(config, stack));
|
||||
}
|
||||
}
|
||||
return validators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Validator> getValidators(Class clazz, String context) {
|
||||
@@ -53,54 +163,189 @@ public class DefaultActionValidatorManager extends AbstractActionValidatorManage
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
|
||||
String validatorKey = buildValidatorKey(clazz, context);
|
||||
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
|
||||
List<Validator> validators = getValidators(object.getClass(), context, method);
|
||||
Set<String> shortcircuitedFields = null;
|
||||
|
||||
if (validatorCache.containsKey(validatorKey)) {
|
||||
if (reloadingConfigs) {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
|
||||
for (Validator validator : validators) {
|
||||
validator.setValidatorContext(validatorContext);
|
||||
|
||||
LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
|
||||
|
||||
FieldValidator fValidator = null;
|
||||
String fullFieldName = null;
|
||||
|
||||
if (validator instanceof FieldValidator) {
|
||||
fValidator = (FieldValidator) validator;
|
||||
fullFieldName = validatorContext.getFullFieldName(fValidator.getFieldName());
|
||||
|
||||
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
|
||||
LOG.debug("Short-circuited, skipping");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
|
||||
}
|
||||
ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
|
||||
// get the set of validator configs
|
||||
List<ValidatorConfig> cfgs = validatorCache.get(validatorKey);
|
||||
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
|
||||
// get number of existing errors
|
||||
List<String> errs = null;
|
||||
|
||||
// create clean instances of the validators for the caller's use
|
||||
ArrayList<Validator> validators = new ArrayList<>(cfgs.size());
|
||||
for (ValidatorConfig cfg : cfgs) {
|
||||
if (method == null || method.equals(cfg.getParams().get("methodName"))) {
|
||||
Validator validator = validatorFactory.getValidator(cfg);
|
||||
validator.setValidatorType(cfg.getType());
|
||||
validator.setValueStack(stack);
|
||||
validators.add(validator);
|
||||
if (fValidator != null) {
|
||||
if (validatorContext.hasFieldErrors()) {
|
||||
Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
|
||||
|
||||
if (fieldErrors != null) {
|
||||
errs = new ArrayList<>(fieldErrors);
|
||||
}
|
||||
}
|
||||
} else if (validatorContext.hasActionErrors()) {
|
||||
Collection<String> actionErrors = validatorContext.getActionErrors();
|
||||
|
||||
if (actionErrors != null) {
|
||||
errs = new ArrayList<>(actionErrors);
|
||||
}
|
||||
}
|
||||
|
||||
validator.validate(object);
|
||||
|
||||
if (fValidator != null) {
|
||||
if (validatorContext.hasFieldErrors()) {
|
||||
Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
|
||||
|
||||
if ((errCol != null) && !errCol.equals(errs)) {
|
||||
LOG.debug("Short-circuiting on field validation");
|
||||
|
||||
if (shortcircuitedFields == null) {
|
||||
shortcircuitedFields = new TreeSet<>();
|
||||
}
|
||||
|
||||
shortcircuitedFields.add(fullFieldName);
|
||||
}
|
||||
}
|
||||
} else if (validatorContext.hasActionErrors()) {
|
||||
Collection<String> errCol = validatorContext.getActionErrors();
|
||||
|
||||
if ((errCol != null) && !errCol.equals(errs)) {
|
||||
LOG.debug("Short-circuiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
validator.validate(object);
|
||||
}
|
||||
return validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a key for validators - used when caching validators.
|
||||
* <p>This method 'collects' all the validator configurations for a given
|
||||
* action invocation.</p>
|
||||
*
|
||||
* @param clazz the action.
|
||||
* @param context the action's context.
|
||||
* @return a validator key which is the class name plus context.
|
||||
* <p>It will traverse up the class hierarchy looking for validators for every super class
|
||||
* and directly implemented interface of the current action, as well as adding validators for
|
||||
* any alias of this invocation. Nifty!</p>
|
||||
*
|
||||
* <p>Given the following class structure:</p>
|
||||
* <pre>
|
||||
* interface Thing;
|
||||
* interface Animal extends Thing;
|
||||
* interface Quadraped extends Animal;
|
||||
* class AnimalImpl implements Animal;
|
||||
* class QuadrapedImpl extends AnimalImpl implements Quadraped;
|
||||
* class Dog extends QuadrapedImpl;
|
||||
* </pre>
|
||||
*
|
||||
* <p>This method will look for the following config files for Dog:</p>
|
||||
* <pre>
|
||||
* Animal
|
||||
* Animal-context
|
||||
* AnimalImpl
|
||||
* AnimalImpl-context
|
||||
* Quadraped
|
||||
* Quadraped-context
|
||||
* QuadrapedImpl
|
||||
* QuadrapedImpl-context
|
||||
* Dog
|
||||
* Dog-context
|
||||
* </pre>
|
||||
*
|
||||
* <p>Note that the validation rules for Thing is never looked for because no class in the
|
||||
* hierarchy directly implements Thing.</p>
|
||||
*
|
||||
* @param clazz the Class to look up validators for.
|
||||
* @param context the context to use when looking up validators.
|
||||
* @param checkFile true if the validation config file should be checked to see if it has been
|
||||
* updated.
|
||||
* @param checked the set of previously checked class-contexts, null if none have been checked
|
||||
* @return a list of validator configs for the given class and context.
|
||||
*/
|
||||
protected static String buildValidatorKey(Class clazz, String context) {
|
||||
return clazz.getName() + "/" + context;
|
||||
protected List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
|
||||
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
|
||||
|
||||
if (checked == null) {
|
||||
checked = new TreeSet<>();
|
||||
} else if (checked.contains(clazz.getName())) {
|
||||
return validatorConfigs;
|
||||
}
|
||||
|
||||
if (clazz.isInterface()) {
|
||||
for (Class anInterface : clazz.getInterfaces()) {
|
||||
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
|
||||
}
|
||||
} else {
|
||||
if (!clazz.equals(Object.class)) {
|
||||
validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
|
||||
}
|
||||
}
|
||||
|
||||
// look for validators for implemented interfaces
|
||||
for (Class anInterface1 : clazz.getInterfaces()) {
|
||||
if (checked.contains(anInterface1.getName())) {
|
||||
continue;
|
||||
}
|
||||
validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
|
||||
if (context != null) {
|
||||
validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
|
||||
}
|
||||
checked.add(anInterface1.getName());
|
||||
}
|
||||
|
||||
validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
|
||||
if (context != null) {
|
||||
validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
|
||||
}
|
||||
checked.add(clazz.getName());
|
||||
|
||||
return validatorConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
|
||||
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
|
||||
return loadFile(fileName, aClass, checkFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
|
||||
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
|
||||
return loadFile(fileName, aClass, checkFile);
|
||||
}
|
||||
|
||||
protected List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
|
||||
List<ValidatorConfig> retList = Collections.emptyList();
|
||||
|
||||
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
|
||||
|
||||
if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
|
||||
try (InputStream is = fileManager.loadFile(fileUrl)) {
|
||||
if (is != null) {
|
||||
retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Caught exception while closing file {}", fileName, e);
|
||||
}
|
||||
|
||||
validatorFileCache.put(fileName, retList);
|
||||
} else {
|
||||
retList = validatorFileCache.get(fileName);
|
||||
}
|
||||
|
||||
return retList;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-33
@@ -44,7 +44,7 @@ import java.util.List;
|
||||
* DefaultActionValidatorManagerTest
|
||||
*
|
||||
* @author Jason Carreira
|
||||
* @author tm_jee
|
||||
* @author tm_jee
|
||||
* @version $Date$ $Id$
|
||||
*/
|
||||
public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
@@ -87,7 +87,7 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
|
||||
|
||||
public void testBuildValidatorKey() {
|
||||
String validatorKey = DefaultActionValidatorManager.buildValidatorKey(SimpleAction.class, alias);
|
||||
String validatorKey = actionValidatorManager.buildValidatorKey(SimpleAction.class, alias);
|
||||
assertEquals(SimpleAction.class.getName() + "/" + alias, validatorKey);
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
user.setName("Mark");
|
||||
// * mark both email to starts with mark to get pass the action-level validator,
|
||||
// so we could concentrate on testing the field-level validators (User-validation.xml)
|
||||
// * make both email the same to pass the action-level validator at
|
||||
// * make both email the same to pass the action-level validator at
|
||||
// UserMarker-validation.xml
|
||||
user.setEmail("mark_bad_email_for_field_val@foo.com");
|
||||
user.setEmail2("mark_bad_email_for_field_val@foo.com");
|
||||
@@ -255,51 +255,51 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
assertEquals(1, l.size()); // because email-field-val is short-circuit
|
||||
assertEquals("Email not from the right company.", l.get(0));
|
||||
|
||||
|
||||
|
||||
// check action errors
|
||||
l = (List) context.getActionErrors();
|
||||
assertFalse(context.hasActionErrors());
|
||||
assertEquals(0, l.size());
|
||||
|
||||
|
||||
|
||||
|
||||
} catch (ValidationException ex) {
|
||||
ex.printStackTrace();
|
||||
fail("Validation error: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void testActionLevelShortCircuit() throws Exception {
|
||||
|
||||
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
assertEquals(10, validatorList.size());
|
||||
|
||||
|
||||
User user = new User();
|
||||
// all fields will trigger error, but sc of action-level, cause it to not appear
|
||||
user.setName(null);
|
||||
user.setName(null);
|
||||
user.setEmail("tmjee(at)yahoo.co.uk");
|
||||
user.setEmail("tm_jee(at)yahoo.co.uk");
|
||||
|
||||
|
||||
ValidatorContext context = new GenericValidatorContext(user);
|
||||
actionValidatorManager.validate(user, null, context);
|
||||
|
||||
|
||||
// check field level errors
|
||||
// shouldn't have any because action error prevents validation of anything else
|
||||
List l = (List) context.getFieldErrors().get("email2");
|
||||
assertNull(l);
|
||||
|
||||
|
||||
|
||||
|
||||
// check action errors
|
||||
assertTrue(context.hasActionErrors());
|
||||
l = (List) context.getActionErrors();
|
||||
assertNotNull(l);
|
||||
// we only get one, because UserMarker-validation.xml action-level validator
|
||||
// already sc it :-)
|
||||
assertEquals(1, l.size());
|
||||
assertEquals(1, l.size());
|
||||
assertEquals("Email not the same as email2", l.get(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void testShortCircuitNoErrors() {
|
||||
// get validators
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
@@ -319,65 +319,65 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
fail("Validation error: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testFieldErrorsOrder() throws Exception {
|
||||
ValidationOrderAction action = new ValidationOrderAction();
|
||||
actionValidatorManager.validate(action, "actionContext");
|
||||
Map fieldErrors = action.getFieldErrors();
|
||||
Iterator i = fieldErrors.entrySet().iterator();
|
||||
|
||||
|
||||
assertNotNull(fieldErrors);
|
||||
assertEquals(fieldErrors.size(), 12);
|
||||
|
||||
|
||||
|
||||
|
||||
Map.Entry e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "username");
|
||||
assertEquals(((List)e.getValue()).get(0), "username required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "password");
|
||||
assertEquals(((List)e.getValue()).get(0), "password required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "confirmPassword");
|
||||
assertEquals(((List)e.getValue()).get(0), "confirm password required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "firstName");
|
||||
assertEquals(((List)e.getValue()).get(0), "first name required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "lastName");
|
||||
assertEquals(((List)e.getValue()).get(0), "last name required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "city");
|
||||
assertEquals(((List)e.getValue()).get(0), "city is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "province");
|
||||
assertEquals(((List)e.getValue()).get(0), "province is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "country");
|
||||
assertEquals(((List)e.getValue()).get(0), "country is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "postalCode");
|
||||
assertEquals(((List)e.getValue()).get(0), "postal code is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "email");
|
||||
assertEquals(((List)e.getValue()).get(0), "email is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "website");
|
||||
assertEquals(((List)e.getValue()).get(0), "website is required");
|
||||
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "passwordHint");
|
||||
assertEquals(((List)e.getValue()).get(0), "password hint is required");
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user