blob: 5e36f8158028cb2390a81a9a2b5eeb25a2e56da0 [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;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.providers.XmlHelper;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.DomHelper;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import java.io.InputStream;
import java.util.*;
/**
* Parse the validation file. (eg. MyAction-validation.xml, MyAction-actionAlias-validation.xml)
* to return a List of ValidatorConfig encapsulating the validator information.
*
* @author Jason Carreira
* @author James House
* @author tm_jee ( tm_jee (at) yahoo.co.uk )
* @author Rob Harrop
* @author Rene Gielen
* @author Martin Gilday
*
* @see com.opensymphony.xwork2.validator.ValidatorConfig
*/
public class DefaultValidatorFileParser implements ValidatorFileParser {
private static Logger LOG = LogManager.getLogger(DefaultValidatorFileParser.class);
static final String DEFAULT_MULTI_TEXTVALUE_SEPARATOR = " ";
static final String MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY = "xwork.validatorfileparser.multi_textvalue_separator";
private ObjectFactory objectFactory;
private String multiTextvalueSeparator=DEFAULT_MULTI_TEXTVALUE_SEPARATOR;
@Inject(value=MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY, required = false)
public void setMultiTextvalueSeparator(String type) {
multiTextvalueSeparator = type;
}
public String getMultiTextvalueSeparator() {
return multiTextvalueSeparator;
}
@Inject
public void setObjectFactory(ObjectFactory fac) {
this.objectFactory = fac;
}
public List<ValidatorConfig> parseActionValidatorConfigs(ValidatorFactory validatorFactory, InputStream is, final String resourceName) {
List<ValidatorConfig> validatorCfgs = new ArrayList<>();
InputSource in = new InputSource(is);
in.setSystemId(resourceName);
Map<String, String> dtdMappings = new HashMap<>();
dtdMappings.put("-//Apache Struts//XWork Validator 1.0//EN", "xwork-validator-1.0.dtd");
dtdMappings.put("-//Apache Struts//XWork Validator 1.0.2//EN", "xwork-validator-1.0.2.dtd");
dtdMappings.put("-//Apache Struts//XWork Validator 1.0.3//EN", "xwork-validator-1.0.3.dtd");
dtdMappings.put("-//Apache Struts//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd");
Document doc = DomHelper.parse(in, dtdMappings);
if (doc != null) {
NodeList fieldNodes = doc.getElementsByTagName("field");
// BUG: xw-305: Let validator be parsed first and hence added to
// the beginning of list and therefore evaluated first, so short-circuting
// it will not cause field-level validator to be kicked off.
{
NodeList validatorNodes = doc.getElementsByTagName("validator");
addValidatorConfigs(validatorFactory, validatorNodes, new HashMap<String, Object>(), validatorCfgs);
}
for (int i = 0; i < fieldNodes.getLength(); i++) {
Element fieldElement = (Element) fieldNodes.item(i);
String fieldName = fieldElement.getAttribute("name");
Map<String, Object> extraParams = new HashMap<String, Object>();
extraParams.put("fieldName", fieldName);
NodeList validatorNodes = fieldElement.getElementsByTagName("field-validator");
addValidatorConfigs(validatorFactory, validatorNodes, extraParams, validatorCfgs);
}
}
return validatorCfgs;
}
public void parseValidatorDefinitions(Map<String, String> validators, InputStream is, String resourceName) {
InputSource in = new InputSource(is);
in.setSystemId(resourceName);
Map<String, String> dtdMappings = new HashMap<>();
dtdMappings.put("-//Apache Struts//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd");
dtdMappings.put("-//Apache Struts//XWork Validator Definition 1.0//EN", "xwork-validator-definition-1.0.dtd");
Document doc = DomHelper.parse(in, dtdMappings);
if (doc != null) {
NodeList nodes = doc.getElementsByTagName("validator");
for (int i = 0; i < nodes.getLength(); i++) {
Element validatorElement = (Element) nodes.item(i);
String name = validatorElement.getAttribute("name");
String className = validatorElement.getAttribute("class");
try {
// catch any problems here
objectFactory.buildValidator(className, new HashMap<String, Object>(), ActionContext.getContext().getContextMap());
validators.put(name, className);
} catch (Exception e) {
throw new ConfigurationException("Unable to load validator class " + className, e, validatorElement);
}
}
}
}
/**
* Extract trimmed text value from the given DOM element, ignoring XML comments. Appends all CharacterData nodes
* and EntityReference nodes into a single String value, excluding Comment nodes.
* This method is based on a method originally found in DomUtils class of Springframework.
*
* @param valueEle the given DOM element
* @return the text value
*
* @see org.w3c.dom.CharacterData
* @see org.w3c.dom.EntityReference
* @see org.w3c.dom.Comment
*/
public String getTextValue(Element valueEle) {
StringBuilder value = new StringBuilder();
NodeList nl = valueEle.getChildNodes();
boolean firstCDataFound = false;
for (int i = 0; i < nl.getLength(); i++) {
Node item = nl.item(i);
if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
final String nodeValue = item.getNodeValue();
if (nodeValue != null) {
if (firstCDataFound) {
value.append(getMultiTextvalueSeparator());
} else {
firstCDataFound = true;
}
value.append(nodeValue.trim());
}
}
}
return value.toString().trim();
}
private void addValidatorConfigs(ValidatorFactory factory, NodeList validatorNodes, Map<String, Object> extraParams, List<ValidatorConfig> validatorCfgs) {
for (int j = 0; j < validatorNodes.getLength(); j++) {
Element validatorElement = (Element) validatorNodes.item(j);
String validatorType = validatorElement.getAttribute("type");
Map<String, Object> params = new HashMap<String, Object>(extraParams);
params.putAll(XmlHelper.getParams(validatorElement));
// ensure that the type is valid...
try {
factory.lookupRegisteredValidatorType(validatorType);
} catch (IllegalArgumentException ex) {
throw new ConfigurationException("Invalid validation type: " + validatorType, validatorElement);
}
ValidatorConfig.Builder vCfg = new ValidatorConfig.Builder(validatorType)
.addParams(params)
.location(DomHelper.getLocationObject(validatorElement))
.shortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit")));
NodeList messageNodes = validatorElement.getElementsByTagName("message");
Element messageElement = (Element) messageNodes.item(0);
final Node defaultMessageNode = messageElement.getFirstChild();
String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode.getNodeValue();
vCfg.defaultMessage(defaultMessage);
Map<String, String> messageParams = XmlHelper.getParams(messageElement);
String key = messageElement.getAttribute("key");
if ((key != null) && (key.trim().length() > 0)) {
vCfg.messageKey(key);
// Get the default message when pattern 2 is used. We are only interested in the
// i18n message parameters when an i18n message key is specified.
// pattern 1:
// <message key="someKey">Default message</message>
// pattern 2:
// <message key="someKey">
// <param name="1">'param1'</param>
// <param name="2">'param2'</param>
// <param name="defaultMessage>The Default Message</param>
// </message>
if (messageParams.containsKey("defaultMessage")) {
vCfg.defaultMessage(messageParams.get("defaultMessage"));
}
// Sort the message param. those with keys as '1', '2', '3' etc. (numeric values)
// are i18n message parameter, others are excluded.
TreeMap<Integer, String> sortedMessageParameters = new TreeMap<Integer, String>();
for (Map.Entry<String, String> messageParamEntry : messageParams.entrySet()) {
try {
int _order = Integer.parseInt(messageParamEntry.getKey());
sortedMessageParameters.put(_order, messageParamEntry.getValue());
}
catch (NumberFormatException e) {
// ignore if its not numeric.
}
}
vCfg.messageParams(sortedMessageParameters.values().toArray(new String[sortedMessageParameters.values().size()]));
} else {
if (messageParams != null && (messageParams.size() > 0)) {
// we are i18n message parameters defined but no i18n message,
// let's warn the user.
if (LOG.isWarnEnabled()) {
LOG.warn("validator of type ["+validatorType+"] have i18n message parameters defined but no i18n message key, it's parameters will be ignored");
}
}
}
validatorCfgs.add(vCfg.build());
}
}
}