blob: f39bb2975ef9e3610687a0f698d53a778adb13f7 [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.xmlbeans.impl.validator;
import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.common.*;
import org.apache.xmlbeans.impl.schema.SchemaTypeImpl;
import org.apache.xmlbeans.impl.schema.SchemaTypeVisitorImpl;
import org.apache.xmlbeans.impl.util.XsTypeConverter;
import org.apache.xmlbeans.impl.values.*;
import javax.xml.namespace.QName;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
public final class Validator
implements ValidatorListener {
public Validator(
SchemaType type, SchemaField field, SchemaTypeLoader globalLoader,
XmlOptions options, Collection<XmlError> defaultErrorListener) {
options = XmlOptions.maskNull(options);
_errorListener = options.getErrorListener();
_treatLaxAsSkip = options.isValidateTreatLaxAsSkip();
_strict = options.isValidateStrict();
if (_errorListener == null) {
_errorListener = defaultErrorListener;
}
_constraintEngine = new IdentityConstraint(_errorListener, type.isDocumentType());
_globalTypes = globalLoader;
_rootType = type;
_rootField = field;
_vc = new ValidatorVC();
}
private class ValidatorVC implements ValidationContext {
// KHK: remove this
public void invalid(String message) {
// TODO (dutta) Addtional Attributes for validation error have limited information
//at this time but will be a part of the second round of refactoring
Validator.this.emitError(_event, message, null, null,
XmlValidationError.ATTRIBUTE_TYPE_INVALID);
}
public void invalid(String code, Object[] args) {
// TODO (dutta) Addtional Attributes for validation error have limited information
//at this time but will be a part of the second round of refactoring
Validator.this.emitError(_event, code, args, null, null,
XmlValidationError.ATTRIBUTE_TYPE_INVALID, null);
}
Event _event;
}
public boolean isValid() {
return !_invalid && _constraintEngine.isValid();
}
// KHK: remove this
private void emitError(Event event, String message, QName offendingQName,
SchemaType expectedSchemaType,
int errorType) {
emitError(event, message, null, null, XmlError.SEVERITY_ERROR, null, offendingQName, expectedSchemaType,
null, errorType, null);
}
private void emitError(Event event, String code, Object[] args, QName offendingQName,
SchemaType expectedSchemaType,
int errorType, SchemaType badSchemaType) {
emitError(event, null, code, args, XmlError.SEVERITY_ERROR, null, offendingQName, expectedSchemaType,
null, errorType, badSchemaType);
}
// KHK: remove 'message' parameter
private void emitError(Event event, String message, String code, Object[] args, int severity,
QName fieldName, QName offendingQName,
SchemaType expectedSchemaType, List<QName> expectedQNames,
int errorType, SchemaType badSchemaType) {
_errorState++;
if (_suspendErrors == 0) {
if (severity == XmlError.SEVERITY_ERROR) {
_invalid = true;
}
if (_errorListener != null) {
assert event != null;
XmlError error;
XmlCursor curs = event.getLocationAsCursor();
if (curs != null) {
// non-streaming validation uses XmlCursor
error = XmlValidationError.forCursorWithDetails(message, code, args, severity,
curs, fieldName, offendingQName, expectedSchemaType, expectedQNames,
errorType, badSchemaType);
} else {
// streaming validation uses Location
error = XmlValidationError.forLocationWithDetails(message, code, args, severity,
event.getLocation(), fieldName, offendingQName, expectedSchemaType, expectedQNames,
errorType, badSchemaType);
}
_errorListener.add(error);
}
}
}
private void emitFieldError(Event event, String code, Object[] args, QName offendingQName,
SchemaType expectedSchemaType, List<QName> expectedQNames,
int errorType, SchemaType badSchemaType) {
QName fieldName = null;
if (_stateStack != null && _stateStack._field != null) {
fieldName = _stateStack._field.getName();
}
Validator.this.emitError(event, null, code, args, XmlError.SEVERITY_ERROR, fieldName, offendingQName, expectedSchemaType,
expectedQNames, errorType, badSchemaType);
}
public void nextEvent(int kind, Event event) {
resetValues();
if (_eatContent > 0) {
switch (kind) {
case END:
_eatContent--;
break;
case BEGIN:
_eatContent++;
break;
}
} else {
assert
kind == BEGIN || kind == ATTR ||
kind == END || kind == TEXT || kind == ENDATTRS;
switch (kind) {
case BEGIN:
beginEvent(event);
break;
case ATTR:
attrEvent(event);
break;
case ENDATTRS:
endAttrsEvent(event);
break;
case TEXT:
textEvent(event);
break;
case END:
endEvent(event);
break;
}
}
}
private void beginEvent(Event event) {
_localElement = null;
_wildcardElement = null;
State state = topState();
SchemaType elementType;
SchemaField elementField;
if (state == null) {
elementType = _rootType;
elementField = _rootField;
} else {
QName name = event.getName();
assert name != null;
state._isEmpty = false;
if (state._isNil) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$NIL_WITH_CONTENT,
null, state._field.getName(), state._type, null,
XmlValidationError.NIL_ELEMENT, state._type);
_eatContent = 1;
return;
}
if (state._field != null && state._field.isFixed()) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$FIXED_WITH_CONTENT,
new Object[]{QNameHelper.pretty(state._field.getName())},
state._field.getName(), state._type, null,
XmlValidationError.ELEMENT_NOT_ALLOWED, state._type);
}
if (!state.visit(name)) {
findDetailedErrorBegin(event, state, name);
_eatContent = 1;
return;
}
SchemaParticle currentParticle = state.currentParticle();
_wildcardElement = currentParticle;
if (currentParticle.getParticleType() == SchemaParticle.WILDCARD) {
//_wildcardElement = currentParticle;
QNameSet elemWildcardSet = currentParticle.getWildcardSet();
if (!elemWildcardSet.contains(name)) {
// Additional processing may be needed to generate more
// descriptive messages
// KHK: cvc-complex-type.2.4? cvc-particle.1.3? cvc-wildcard-namespace ?
emitFieldError(event, XmlErrorCodes.PARTICLE_VALID$NOT_WILDCARD_VALID,
new Object[]{QNameHelper.pretty(name)},
name, null, null,
XmlValidationError.ELEMENT_NOT_ALLOWED, state._type);
_eatContent = 1;
return;
}
int wildcardProcess = currentParticle.getWildcardProcess();
if (wildcardProcess == SchemaParticle.SKIP ||
wildcardProcess == SchemaParticle.LAX && _treatLaxAsSkip) {
_eatContent = 1;
return;
}
_localElement = _globalTypes.findElement(name);
elementField = _localElement;
if (elementField == null) {
if (wildcardProcess == SchemaParticle.STRICT) {
// KHK: cvc-complex-type.2.4c? cvc-assess-elt.1.1.1.3.2?
emitFieldError(event, XmlErrorCodes.ASSESS_ELEM_SCHEMA_VALID$NOT_RESOLVED,
new Object[]{QNameHelper.pretty(name)},
name, state._type, null,
XmlValidationError.ELEMENT_NOT_ALLOWED, state._type);
}
_eatContent = 1;
return;
}
} else {
assert currentParticle.getParticleType() == SchemaParticle.ELEMENT;
// If the current element particle name does not match the name
// of the event, then the current element is a substitute for
// the current particle. Replace the field with the global
// element for the replacement
if (!currentParticle.getName().equals(name)) {
if (((SchemaLocalElement) currentParticle).blockSubstitution()) {
emitFieldError(event, XmlErrorCodes.PARTICLE_VALID$BLOCK_SUBSTITUTION,
new Object[]{QNameHelper.pretty(name)},
name, state._type, null,
XmlValidationError.ELEMENT_NOT_ALLOWED, state._type);
_eatContent = 1;
return;
}
SchemaGlobalElement newField = _globalTypes.findElement(name);
assert newField != null;
elementField = newField;
_localElement = newField;
} else {
elementField = (SchemaField) currentParticle;
}
}
elementType = elementField.getType();
}
assert elementType != null;
//
// the no-type is always invalid (even if there is an xsi:type)
//
if (elementType.isNoType()) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$NO_TYPE,
null, event.getName(), null, null,
XmlValidationError.ELEMENT_TYPE_INVALID, null);
_eatContent = 1;
}
//
// See if the element has an xsi:type on it
//
SchemaType xsiType = null;
String value = event.getXsiType();
if (value != null) {
// Turn off the listener so a public error message
// does not get generated, but I can see if there was
// an error through the error state
int originalErrorState = _errorState;
_suspendErrors++;
try {
_vc._event = null;
xsiType = _globalTypes.findType(XmlQNameImpl.validateLexical(value, _vc, event));
} catch (Throwable t) {
_errorState++;
} finally {
_suspendErrors--;
}
if (originalErrorState != _errorState) {
// not sure how to extract this one
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_INVALID_QNAME,
new Object[]{value}, event.getName(), xsiType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
} else if (xsiType == null) {
// NOT SURE errorAttributes._expectedSchemaType = xsiType;
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_NOT_FOUND,
new Object[]{value}, event.getName(), null, null,
XmlValidationError.ELEMENT_TYPE_INVALID, null);
_eatContent = 1;
return;
}
}
if (xsiType != null && !xsiType.equals(elementType)) {
if (!elementType.isAssignableFrom(xsiType)) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_NOT_DERIVED,
new Object[]{xsiType, elementType}, event.getName(), elementType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
}
if (elementType.blockExtension()) {
for (SchemaType t = xsiType; !t.equals(elementType);
t = t.getBaseType()) {
if (t.getDerivationType() == SchemaType.DT_EXTENSION) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_BLOCK_EXTENSION,
new Object[]{xsiType, elementType}, event.getName(), elementType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
}
}
}
if (elementType.blockRestriction()) {
for (SchemaType t = xsiType; !t.equals(elementType);
t = t.getBaseType()) {
if (t.getDerivationType() == SchemaType.DT_RESTRICTION) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_BLOCK_RESTRICTION,
new Object[]{xsiType, elementType}, event.getName(), elementType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
}
}
}
if (elementField instanceof SchemaLocalElement) {
SchemaLocalElement sle = (SchemaLocalElement) elementField;
_localElement = sle;
if (sle.blockExtension() || sle.blockRestriction()) {
for (SchemaType t = xsiType; !t.equals(elementType);
t = t.getBaseType()) {
if ((t.getDerivationType() == SchemaType.DT_RESTRICTION && sle.blockRestriction()) ||
(t.getDerivationType() == SchemaType.DT_EXTENSION && sle.blockExtension())) {
//need to find a way to get the right type
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$XSI_TYPE_PROHIBITED_SUBST,
new Object[]{xsiType, QNameHelper.pretty(sle.getName())},
sle.getName(), null, null, XmlValidationError.ELEMENT_TYPE_INVALID, null);
_eatContent = 1;
return;
}
}
}
}
elementType = xsiType;
}
if (elementField instanceof SchemaLocalElement) {
SchemaLocalElement sle = (SchemaLocalElement) elementField;
_localElement = sle;
if (sle.isAbstract()) {
//todo (dutta) need to find a way to get the right type
emitError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$ABSTRACT,
new Object[]{QNameHelper.pretty(sle.getName())},
sle.getName(), null, XmlValidationError.ELEMENT_TYPE_INVALID, null);
_eatContent = 1;
return;
}
}
if (elementType.isAbstract()) {
emitError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$ABSTRACT,
new Object[]{elementType},
event.getName(), elementType, XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
}
boolean isNil = false;
boolean hasNil = false;
String nilValue = event.getXsiNil();
if (nilValue != null) {
_vc._event = event;
isNil = JavaBooleanHolder.validateLexical(nilValue, _vc);
hasNil = true;
}
// note in schema spec 3.3.4, you're not even allowed to say xsi:nil="false" if you're not nillable!
if (hasNil && (elementField == null || !elementField.isNillable())) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$NOT_NILLABLE, null,
elementField == null ? null : elementField.getName(), elementType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
_eatContent = 1;
return;
}
if (isNil && elementField != null && elementField.isFixed()) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$NIL_WITH_FIXED, null,
elementField.getName(), elementType, null,
XmlValidationError.ELEMENT_TYPE_INVALID, (state == null ? null : state._type));
}
newState(elementType, elementField, isNil);
// Dispatch this element event to any identity constraints
// As well as adding any new identity constraints that exist
_constraintEngine.element(
event,
elementType,
elementField instanceof SchemaLocalElement
? ((SchemaLocalElement) elementField).getIdentityConstraints()
: null);
}
private void attrEvent(Event event) {
QName attrName = event.getName();
State state = topState();
if (state._attrs == null) {
state._attrs = new HashSet<>();
}
if (state._attrs.contains(attrName)) {
emitFieldError(event, XmlErrorCodes.XML_DUPLICATE_ATTRIBUTE,
new Object[]{QNameHelper.pretty(attrName)},
attrName, null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
state._attrs.add(attrName);
if (!state._canHaveAttrs) {
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$NO_WILDCARD,
new Object[]{QNameHelper.pretty(attrName)}, attrName, null, null,
XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
SchemaLocalAttribute attrSchema =
state._attrModel == null
? null
: state._attrModel.getAttribute(attrName);
if (attrSchema != null) {
_localAttribute = attrSchema;
if (attrSchema.getUse() == SchemaLocalAttribute.PROHIBITED) {
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$PROHIBITED_ATTRIBUTE,
new Object[]{QNameHelper.pretty(attrName)},
attrName, null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
String value =
validateSimpleType(
attrSchema.getType(), attrSchema, event, false, false);
_constraintEngine.attr(event, attrName, attrSchema.getType(), value);
return;
}
int wildcardProcess = state._attrModel.getWildcardProcess();
_wildcardAttribute = state._attrModel;
if (wildcardProcess == SchemaAttributeModel.NONE) {
// todo (dutta) need additional logic to determine the expectedSchemaType
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$NO_WILDCARD,
new Object[]{QNameHelper.pretty(attrName)},
attrName, null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
QNameSet attrWildcardSet = state._attrModel.getWildcardSet();
if (!attrWildcardSet.contains(attrName)) {
// todo (dutta) need additional logic to determine the expectedSchemaType
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$NOT_WILDCARD_VALID,
new Object[]{QNameHelper.pretty(attrName)},
attrName, null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
if (wildcardProcess == SchemaAttributeModel.SKIP ||
wildcardProcess == SchemaAttributeModel.LAX && _treatLaxAsSkip) {
return;
}
attrSchema = _globalTypes.findAttribute(attrName);
_localAttribute = attrSchema;
if (attrSchema == null) {
if (wildcardProcess == SchemaAttributeModel.LAX) {
return;
}
assert wildcardProcess == SchemaAttributeModel.STRICT;
// KHK: cvc-assess-attr.1.2 ?
// todo (dutta) need additional logic to determine the expectedSchemaType
emitFieldError(event, XmlErrorCodes.ASSESS_ATTR_SCHEMA_VALID$NOT_RESOLVED,
new Object[]{QNameHelper.pretty(attrName)},
attrName, null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
return;
}
String value =
validateSimpleType(
attrSchema.getType(), attrSchema, event, false, false);
_constraintEngine.attr(event, attrName, attrSchema.getType(), value);
}
private void endAttrsEvent(Event event) {
State state = topState();
if (state._attrModel != null) {
SchemaLocalAttribute[] attrs = state._attrModel.getAttributes();
for (SchemaLocalAttribute sla : attrs) {
if (state._attrs == null ||
!state._attrs.contains(sla.getName())) {
if (sla.getUse() == SchemaLocalAttribute.REQUIRED) {
// KHK: cvc-complex-type.4
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$MISSING_REQUIRED_ATTRIBUTE,
new Object[]{QNameHelper.pretty(sla.getName())},
sla.getName(), null, null, XmlValidationError.INCORRECT_ATTRIBUTE, state._type);
} else if (sla.isDefault() || sla.isFixed()) {
_constraintEngine.attr(event, sla.getName(), sla.getType(), sla.getDefaultText());
// We don't need to validate attribute defaults because this is done at compiletime.
/*
String value = sla.getDefaultText();
SchemaType type = sla.getType();
if (XmlQName.type.isAssignableFrom(type))
{
emitFieldError(
event,
"Default QName values are unsupported for attribute: " +
QNameHelper.pretty(sla.getName()),
XmlError.SEVERITY_INFO);
}
else
{
validateSimpleType(
type, sla.getDefaultText(), event );
_constraintEngine.attr( event, type, value );
}
*/
}
}
}
}
}
private void endEvent(Event event) {
_localElement = null;
_wildcardElement = null;
State state = topState();
if (!state._isNil) {
if (!state.end()) {
findDetailedErrorEnd(event, state);
}
// This end event has no text, use this fact to pass no text to
// handleText
if (state._isEmpty) {
handleText(event, true, state._field);
}
}
popState(event);
_constraintEngine.endElement(event);
}
private void textEvent(Event event) {
State state = topState();
if (state._isNil) {
emitFieldError(event, XmlErrorCodes.ELEM_LOCALLY_VALID$NIL_WITH_CONTENT, null,
state._field.getName(), state._type, null,
XmlValidationError.NIL_ELEMENT, state._type);
} else {
handleText(event, false, state._field);
}
state._isEmpty = false;
}
private void handleText(
Event event, boolean emptyContent, SchemaField field) {
State state = topState();
if (!state._sawText) {
if (state._hasSimpleContent) {
String value =
validateSimpleType(
state._type, field, event, emptyContent, true);
_constraintEngine.text(event, state._type, value, false);
} else if (state._canHaveMixedContent) {
// handles cvc-elt.5.2.2.2.1, checking mixed content against fixed.
// if we see <mixedType>a</b>c</mixedType>, we validate against
// the first 'a' text and we check the content of mixedType to
// be empty in beginElem(). we don't care about checking against
// the 'c' text since there will already be an error for <b/>
String value =
validateSimpleType(
XmlString.type, field, event, emptyContent, true);
_constraintEngine.text(event, XmlString.type, value, false);
} else if (emptyContent) {
_constraintEngine.text(event, state._type, null, true);
} else {
_constraintEngine.text(event, state._type, "", false);
}
}
if (!emptyContent && !state._canHaveMixedContent &&
!event.textIsWhitespace() && !state._hasSimpleContent) {
if (field instanceof SchemaLocalElement) {
SchemaLocalElement e = (SchemaLocalElement) field;
assert state._type.getContentType() == SchemaType.EMPTY_CONTENT ||
state._type.getContentType() == SchemaType.ELEMENT_CONTENT;
// KHK: cvc-complex-type.2.1 or .2.3
String errorCode = (state._type.getContentType() == SchemaType.EMPTY_CONTENT ?
XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$EMPTY_WITH_CONTENT :
XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$ELEMENT_ONLY_WITH_TEXT);
emitError(event, errorCode, new Object[]{QNameHelper.pretty(e.getName())},
e.getName(), field.getType(),
XmlValidationError.ELEMENT_TYPE_INVALID, null);
} else {
// KHK: cvc-complex-type.2.1 or .2.3
// todo (dutta) offendingQName = not sure how to get this(event.getName()??);
emitError(event, "Can't have mixed content", event.getName(),
state._type, XmlValidationError.ELEMENT_TYPE_INVALID);
}
}
if (!emptyContent) {
state._sawText = true;
}
}
private void findDetailedErrorBegin(Event event, State state, QName qName) {
ArrayList<QName> expectedNames = new ArrayList<>();
ArrayList<QName> optionalNames = new ArrayList<>();
SchemaProperty[] eltProperties = state._type.getElementProperties();
for (SchemaProperty sProp : eltProperties) {
//Get the element from the schema
// test if the element is valid
if (state.test(sProp.getName())) {
if (0 == BigInteger.ZERO.compareTo(sProp.getMinOccurs())) {
optionalNames.add(sProp.getName());
} else {
expectedNames.add(sProp.getName());
}
}
}
List<QName> names = (expectedNames.size() > 0 ? expectedNames : optionalNames);
if (names.size() > 0) {
String buf = names.stream().map(QNameHelper::pretty).collect(Collectors.joining(" "));
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$EXPECTED_DIFFERENT_ELEMENT,
new Object[]{names.size(), buf, QNameHelper.pretty(qName)},
qName, null, names, XmlValidationError.INCORRECT_ELEMENT, state._type);
} else {
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$ELEMENT_NOT_ALLOWED,
new Object[]{QNameHelper.pretty(qName)},
qName, null, null, XmlValidationError.INCORRECT_ELEMENT, state._type);
}
}
private void findDetailedErrorEnd(Event event, State state) {
SchemaProperty[] eltProperties = state._type.getElementProperties();
ArrayList<QName> expectedNames = new ArrayList<>();
ArrayList<QName> optionalNames = new ArrayList<>();
for (SchemaProperty sProp : eltProperties) {
//Get the element from the schema
// test if the element is valid
if (state.test(sProp.getName())) {
if (0 == BigInteger.ZERO.compareTo(sProp.getMinOccurs())) {
optionalNames.add(sProp.getName());
} else {
expectedNames.add(sProp.getName());
}
}
}
List<QName> names = (expectedNames.size() > 0 ? expectedNames : optionalNames);
if (names.size() > 0) {
String buf = names.stream().map(QNameHelper::pretty).collect(Collectors.joining(" "));
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$MISSING_ELEMENT,
new Object[]{names.size(), buf},
null, null, names, XmlValidationError.INCORRECT_ELEMENT, state._type);
} else {
emitFieldError(event, XmlErrorCodes.ELEM_COMPLEX_TYPE_LOCALLY_VALID$EXPECTED_ELEMENT,
null, null, null, null, XmlValidationError.ELEMENT_NOT_ALLOWED, state._type);
}
}
private static final class State {
boolean visit(QName name) {
return _canHaveElements && _visitor.visit(name);
}
boolean test(QName name) {
return _canHaveElements && _visitor.testValid(name);
}
boolean end() {
return !_canHaveElements || _visitor.visit(null);
}
SchemaParticle currentParticle() {
assert _visitor != null;
return _visitor.currentParticle();
}
SchemaType _type;
SchemaField _field;
boolean _canHaveAttrs;
boolean _canHaveMixedContent;
boolean _hasSimpleContent;
boolean _sawText;
boolean _isEmpty;
boolean _isNil;
SchemaTypeVisitorImpl _visitor;
boolean _canHaveElements;
SchemaAttributeModel _attrModel;
HashSet<QName> _attrs;
State _next;
}
private boolean derivedFromInteger(SchemaType type) {
int btc = type.getBuiltinTypeCode();
while (btc == SchemaType.BTC_NOT_BUILTIN) {
type = type.getBaseType();
btc = type.getBuiltinTypeCode();
}
// This depends on the ordering of the constant values, which is not ideal but is easier
return btc >= SchemaType.BTC_INTEGER && btc <= SchemaType.BTC_UNSIGNED_BYTE;
}
private void newState(SchemaType type, SchemaField field, boolean isNil) {
State state = new State();
state._type = type;
state._field = field;
state._isEmpty = true;
state._isNil = isNil;
if (type.isSimpleType()) {
state._hasSimpleContent = true;
} else {
state._canHaveAttrs = true;
state._attrModel = type.getAttributeModel();
switch (type.getContentType()) {
case SchemaType.EMPTY_CONTENT:
break;
case SchemaType.SIMPLE_CONTENT:
state._hasSimpleContent = true;
break;
case SchemaType.MIXED_CONTENT:
state._canHaveMixedContent = true;
// Fall through
case SchemaType.ELEMENT_CONTENT:
SchemaParticle particle = type.getContentModel();
state._canHaveElements = particle != null;
if (state._canHaveElements) {
state._visitor = initVisitor(particle);
}
break;
default:
throw new RuntimeException("Unexpected content type");
}
}
pushState(state);
}
private void popState(Event e) {
if (_stateStack._visitor != null) {
poolVisitor(_stateStack._visitor);
_stateStack._visitor = null;
}
_stateStack = _stateStack._next;
}
private void pushState(State state) {
state._next = _stateStack;
_stateStack = state;
}
private final LinkedList<TypeStoreVisitor> _visitorPool = new LinkedList<>();
private void poolVisitor(SchemaTypeVisitorImpl visitor) {
_visitorPool.add(visitor);
}
private SchemaTypeVisitorImpl initVisitor(SchemaParticle particle) {
if (_visitorPool.isEmpty()) {
return new SchemaTypeVisitorImpl(particle);
}
SchemaTypeVisitorImpl result =
(SchemaTypeVisitorImpl) _visitorPool.removeLast();
result.init(particle);
return result;
}
private State topState() {
return _stateStack;
}
//
// Simple Type Validation
//
// emptyContent means that you can't use the event to get text: there is
// no text, but you can use the event to do prefix resolution (in the case
// where the default is a qname)
//
private String validateSimpleType(
SchemaType type, SchemaField field, Event event,
boolean emptyContent, boolean canApplyDefault) {
if (!type.isSimpleType() &&
type.getContentType() != SchemaType.SIMPLE_CONTENT) {
assert false;
// throw new RuntimeException( "Not a simple type" );
return null; // should never happen
}
//
// the no-type is always invalid
//
if (type.isNoType()) {
emitError(event, (field.isAttribute() ? XmlErrorCodes.ATTR_LOCALLY_VALID$NO_TYPE : XmlErrorCodes.ELEM_LOCALLY_VALID$NO_TYPE),
null, field.getName(), type, XmlValidationError.ELEMENT_TYPE_INVALID, null);
return null;
}
// Get the value as a string (as normalized by the white space rule
// TODO - will want to optimize this later
String value = "";
if (!emptyContent) {
int wsr = type.getWhiteSpaceRule();
value = wsr == SchemaType.WS_PRESERVE ? event.getText() : event.getText(wsr);
}
// See if I can apply a default/fixed value
if (value.length() == 0 && canApplyDefault && field != null &&
(field.isDefault() || field.isFixed())) {
if (XmlQName.type.isAssignableFrom(type)) {
// TODO: will be fixed in XmlSchema 1.1
emitError(event, "Default QName values are unsupported for " +
QNameHelper.readable(type) + " - ignoring.", null, null,
XmlError.SEVERITY_INFO, field.getName(), null, type, null,
XmlValidationError.ELEMENT_TYPE_INVALID, null);
return null;
}
String defaultValue =
XmlWhitespace.collapse(
field.getDefaultText(), type.getWhiteSpaceRule());
// BUGBUG - should validate defaultValue at compile time
// KHK: cvc-elt.5.1.2 ?
return
validateSimpleType(type, defaultValue, event)
? defaultValue
: null;
}
// KHK: cvc-elt.5.2.1 ?
if (!validateSimpleType(type, value, event)) {
return null;
}
if (field != null && field.isFixed()) {
// TODO - fixed value should have been cooked at compile time
String fixedValue =
XmlWhitespace.collapse(
field.getDefaultText(), type.getWhiteSpaceRule());
if (!validateSimpleType(type, fixedValue, event)) {
return null;
}
XmlObject val = type.newValue(value);
XmlObject def = type.newValue(fixedValue);
if (!val.valueEquals(def)) {
// TODO (dutta) - make this more verbose
if (field.isAttribute()) {
// KHK: check for is cvc-complex-type.3.1 or cvc-au
emitError(event, XmlErrorCodes.ATTR_LOCALLY_VALID$FIXED,
new Object[]{value, fixedValue, QNameHelper.pretty(event.getName())},
null, field.getType(), XmlValidationError.ELEMENT_TYPE_INVALID, null);
} else {
String errorCode = null;
// see rule 5 of cvc-elt: Element Locally Valid (Element)
if (field.getType().getContentType() == SchemaType.MIXED_CONTENT) {
errorCode = XmlErrorCodes.ELEM_LOCALLY_VALID$FIXED_VALID_MIXED_CONTENT;
} else if (type.isSimpleType()) {
errorCode = XmlErrorCodes.ELEM_LOCALLY_VALID$FIXED_VALID_SIMPLE_TYPE;
} else {
assert false : "Element with fixed may not be EMPTY or ELEMENT_ONLY";
}
emitError(event, errorCode, new Object[]{value, fixedValue},
field.getName(), field.getType(), XmlValidationError.ELEMENT_TYPE_INVALID, null);
}
return null;
}
}
return value;
}
private boolean validateSimpleType(
SchemaType type, String value, Event event) {
if (!type.isSimpleType() &&
type.getContentType() != SchemaType.SIMPLE_CONTENT) {
assert false;
throw new RuntimeException("Not a simple type");
}
int retState = _errorState;
switch (type.getSimpleVariety()) {
case SchemaType.ATOMIC:
validateAtomicType(type, value, event);
break;
case SchemaType.UNION:
validateUnionType(type, value, event);
break;
case SchemaType.LIST:
validateListType(type, value, event);
break;
default:
throw new RuntimeException("Unexpected simple variety");
}
return retState == _errorState;
}
private void validateAtomicType(
SchemaType type, String value, Event event) {
// Now we should have only an atomic type to validate
assert type.getSimpleVariety() == SchemaType.ATOMIC;
// Record the current error state to see if any new errors are made
int errorState = _errorState;
_vc._event = event;
switch (type.getPrimitiveType().getBuiltinTypeCode()) {
case SchemaType.BTC_ANY_SIMPLE: {
// Always valid!
_stringValue = value;
break;
}
case SchemaType.BTC_STRING: {
JavaStringEnumerationHolderEx.validateLexical(value, type, _vc);
_stringValue = value;
break;
}
case SchemaType.BTC_DECIMAL: {
JavaDecimalHolderEx.validateLexical(value, type, _vc);
// An additional rule states that if the type is xs:integer or derived from it,
// then the decimal dot is not allowed.
// verify that values extending xsd:integer don't have a decimal point
if (derivedFromInteger(type) && value.lastIndexOf('.') >= 0) {
_vc.invalid(XmlErrorCodes.INTEGER, new Object[]{value});
}
if (errorState == _errorState) {
_decimalValue = new BigDecimal(value);
JavaDecimalHolderEx.validateValue(_decimalValue, type, _vc);
}
break;
}
case SchemaType.BTC_BOOLEAN: {
_booleanValue = JavaBooleanHolderEx.validateLexical(value, type, _vc);
break;
}
case SchemaType.BTC_FLOAT: {
float f =
JavaFloatHolderEx.validateLexical(value, type, _vc);
if (errorState == _errorState) {
JavaFloatHolderEx.validateValue(f, type, _vc);
}
_floatValue = f;
break;
}
case SchemaType.BTC_DOUBLE: {
double d =
JavaDoubleHolderEx.validateLexical(value, type, _vc);
if (errorState == _errorState) {
JavaDoubleHolderEx.validateValue(d, type, _vc);
}
_doubleValue = d;
break;
}
case SchemaType.BTC_QNAME: {
QName n =
JavaQNameHolderEx.validateLexical(
value, type, _vc, event);
if (errorState == _errorState) {
JavaQNameHolderEx.validateValue(n, type, _vc);
}
_qnameValue = n;
break;
}
case SchemaType.BTC_ANY_URI: {
JavaUriHolderEx.validateLexical(value, type, _vc);
// Do strict validation
if (_strict) {
try {
XsTypeConverter.lexAnyURI(value);
} catch (InvalidLexicalValueException ilve) {
_vc.invalid(XmlErrorCodes.ANYURI, new Object[]{value});
}
}
_stringValue = value;
break;
}
case SchemaType.BTC_G_MONTH: {
// In the case of gMonth, there is some strict mode validation to do
if (_strict && value.length() == 6 &&
value.charAt(4) == '-' && value.charAt(5) == '-') {
_vc.invalid(XmlErrorCodes.DATE, new Object[]{value});
}
// Fall through
}
case SchemaType.BTC_DATE_TIME:
case SchemaType.BTC_TIME:
case SchemaType.BTC_DATE:
case SchemaType.BTC_G_YEAR_MONTH:
case SchemaType.BTC_G_YEAR:
case SchemaType.BTC_G_MONTH_DAY:
case SchemaType.BTC_G_DAY: {
GDate d = XmlDateImpl.validateLexical(value, type, _vc);
if (d != null) {
XmlDateImpl.validateValue(d, type, _vc);
}
_gdateValue = d;
break;
}
case SchemaType.BTC_DURATION: {
GDuration d = XmlDurationImpl.validateLexical(value, type, _vc);
if (d != null) {
XmlDurationImpl.validateValue(d, type, _vc);
}
_gdurationValue = d;
break;
}
case SchemaType.BTC_BASE_64_BINARY: {
byte[] v =
JavaBase64HolderEx.validateLexical(value, type, _vc);
if (v != null) {
JavaBase64HolderEx.validateValue(v, type, _vc);
}
_byteArrayValue = v;
break;
}
case SchemaType.BTC_HEX_BINARY: {
byte[] v =
JavaHexBinaryHolderEx.validateLexical(value, type, _vc);
if (v != null) {
JavaHexBinaryHolderEx.validateValue(v, type, _vc);
}
_byteArrayValue = v;
break;
}
case SchemaType.BTC_NOTATION: {
QName n =
JavaNotationHolderEx.validateLexical(
value, type, _vc, event);
if (errorState == _errorState) {
JavaNotationHolderEx.validateValue(n, type, _vc);
}
_qnameValue = n;
break;
}
default:
throw new RuntimeException("Unexpected primitive type code");
}
}
private void validateListType(
SchemaType type, String value, Event event) {
int errorState = _errorState;
if (!type.matchPatternFacet(value)) {
emitError(event, XmlErrorCodes.DATATYPE_VALID$PATTERN_VALID,
new Object[]{"list", value, QNameHelper.readable(type)},
null, type, XmlValidationError.LIST_INVALID, null);
}
String[] items = XmlListImpl.split_list(value);
int i;
XmlObject o;
if ((o = type.getFacet(SchemaType.FACET_LENGTH)) != null) {
if ((i = ((SimpleValue) o).getIntValue()) != items.length) {
//offending Qname not valid
emitError(event, XmlErrorCodes.DATATYPE_LENGTH_VALID$LIST_LENGTH,
new Object[]{value, items.length, i, QNameHelper.readable(type)},
null, type, XmlValidationError.LIST_INVALID, null);
}
}
if ((o = type.getFacet(SchemaType.FACET_MIN_LENGTH)) != null) {
if ((i = ((SimpleValue) o).getIntValue()) > items.length) {
//offending Qname not valid
emitError(event, XmlErrorCodes.DATATYPE_LENGTH_VALID$LIST_LENGTH,
new Object[]{value, items.length, i, QNameHelper.readable(type)},
null, type, XmlValidationError.LIST_INVALID, null);
}
}
if ((o = type.getFacet(SchemaType.FACET_MAX_LENGTH)) != null) {
if ((i = ((SimpleValue) o).getIntValue()) < items.length) {
//offending Qname not valid
emitError(event, XmlErrorCodes.DATATYPE_LENGTH_VALID$LIST_LENGTH,
new Object[]{value, items.length, i, QNameHelper.readable(type)},
null, type, XmlValidationError.LIST_INVALID, null);
}
}
SchemaType itemType = type.getListItemType();
_listValue = new ArrayList<>();
_listTypes = new ArrayList<>();
for (i = 0; i < items.length; i++) {
validateSimpleType(
itemType, items[i], event);
addToList(itemType);
}
// If no errors up to this point, then I can create an
// XmlList from this value and campare it again enums.
if (errorState == _errorState) {
if (type.getEnumerationValues() != null) {
// Lists which contain QNames will need a resolver
NamespaceContext.push(
new NamespaceContext(event));
try {
XmlObject listValue = ((SchemaTypeImpl) type).newValidatingValue(value);
} catch (XmlValueOutOfRangeException e) {
//offending Qname not valid ??
emitError(event, XmlErrorCodes.DATATYPE_ENUM_VALID,
new Object[]{"list", value, QNameHelper.readable(type)},
null, type, XmlValidationError.LIST_INVALID, null);
} finally {
NamespaceContext.pop();
}
}
}
}
private void validateUnionType(
SchemaType type, String value, Event event) {
// TODO - if xsi:type is specified on a union, it selects
// that union member type
if (!type.matchPatternFacet(value)) {
//offending Qname not valid ??
emitError(event, XmlErrorCodes.DATATYPE_VALID$PATTERN_VALID,
new Object[]{"union", value, QNameHelper.readable(type)},
null, type, XmlValidationError.UNION_INVALID, null);
}
int currentWsr = SchemaType.WS_PRESERVE;
String currentValue = value;
SchemaType[] types = type.getUnionMemberTypes();
int originalState = _errorState;
int i;
for (i = 0; i < types.length; i++) {
int memberWsr = types[i].getWhiteSpaceRule();
if (memberWsr == SchemaType.WS_UNSPECIFIED) {
memberWsr = SchemaType.WS_PRESERVE;
}
if (memberWsr != currentWsr) {
currentWsr = memberWsr;
currentValue = XmlWhitespace.collapse(value, currentWsr);
}
int originalErrorState = _errorState;
_suspendErrors++;
try {
validateSimpleType(types[i], currentValue, event);
} finally {
_suspendErrors--;
}
if (originalErrorState == _errorState) {
_unionType = types[i];
break;
}
}
_errorState = originalState;
if (i >= types.length) {
//offending Qname not valid ??
emitError(event, XmlErrorCodes.DATATYPE_VALID$UNION,
new Object[]{value, QNameHelper.readable(type)},
null, type, XmlValidationError.UNION_INVALID, null);
} else {
XmlObject[] unionEnumvals = type.getEnumerationValues();
if (unionEnumvals != null) {
// Unions which contain QNames will need a resolver
NamespaceContext.push(new NamespaceContext(event));
try {
XmlObject unionValue = type.newValue(value);
for (i = 0; i < unionEnumvals.length; i++) {
if (unionValue.valueEquals(unionEnumvals[i])) {
break;
}
}
if (i >= unionEnumvals.length) {
//offending Qname not valid ??
emitError(event, XmlErrorCodes.DATATYPE_ENUM_VALID,
new Object[]{"union", value, QNameHelper.readable(type)},
null, type, XmlValidationError.UNION_INVALID, null);
}
} catch (XmlValueOutOfRangeException e) {
// actually, the current union code always ends up here when invalid
//offending Qname not valid ??
emitError(event, XmlErrorCodes.DATATYPE_ENUM_VALID,
new Object[]{"union", value, QNameHelper.readable(type)},
null, type, XmlValidationError.UNION_INVALID, null);
} finally {
NamespaceContext.pop();
}
}
}
}
private void addToList(SchemaType type) {
if (type.getSimpleVariety() != SchemaType.ATOMIC &&
type.getSimpleVariety() != SchemaType.UNION) {
return;
}
if (type.getUnionMemberTypes().length > 0 && getUnionType() != null) {
type = getUnionType();
_unionType = null;
}
_listTypes.add(type);
if (type.getPrimitiveType() == null) {
// instance has an error for this value so there is no primitive type.
// an error should already have been produced.
_listValue.add(null);
return;
}
switch (type.getPrimitiveType().getBuiltinTypeCode()) {
case SchemaType.BTC_ANY_SIMPLE:
case SchemaType.BTC_ANY_URI: {
_listValue.add(_stringValue);
break;
}
case SchemaType.BTC_STRING: {
_listValue.add(_stringValue);
_stringValue = null;
break;
}
case SchemaType.BTC_DECIMAL: {
_listValue.add(_decimalValue);
_decimalValue = null;
break;
}
case SchemaType.BTC_BOOLEAN: {
_listValue.add(_booleanValue ? Boolean.TRUE : Boolean.FALSE);
_booleanValue = false;
break;
}
case SchemaType.BTC_FLOAT: {
_listValue.add(_floatValue);
_floatValue = 0;
break;
}
case SchemaType.BTC_DOUBLE: {
_listValue.add(_doubleValue);
_doubleValue = 0;
break;
}
case SchemaType.BTC_QNAME:
case SchemaType.BTC_NOTATION: {
_listValue.add(_qnameValue);
_qnameValue = null;
break;
}
case SchemaType.BTC_DATE_TIME:
case SchemaType.BTC_TIME:
case SchemaType.BTC_DATE:
case SchemaType.BTC_G_YEAR_MONTH:
case SchemaType.BTC_G_YEAR:
case SchemaType.BTC_G_MONTH_DAY:
case SchemaType.BTC_G_DAY:
case SchemaType.BTC_G_MONTH: {
_listValue.add(_gdateValue);
_gdateValue = null;
break;
}
case SchemaType.BTC_DURATION: {
_listValue.add(_gdurationValue);
_gdurationValue = null;
break;
}
case SchemaType.BTC_BASE_64_BINARY:
case SchemaType.BTC_HEX_BINARY: {
_listValue.add(_byteArrayValue);
_byteArrayValue = null;
break;
}
default:
throw new RuntimeException("Unexpected primitive type code");
}
}
//
// Members of the validator class
//
private boolean _invalid;
private final SchemaType _rootType;
private final SchemaField _rootField;
private final SchemaTypeLoader _globalTypes;
private State _stateStack;
private int _errorState;
private Collection<XmlError> _errorListener;
private final boolean _treatLaxAsSkip;
private final boolean _strict;
private final ValidatorVC _vc;
private int _suspendErrors;
private final IdentityConstraint _constraintEngine;
private int _eatContent;
private SchemaLocalElement _localElement;
private SchemaParticle _wildcardElement;
private SchemaLocalAttribute _localAttribute;
private SchemaAttributeModel _wildcardAttribute;
private SchemaType _unionType;
// Strongly typed values
private String _stringValue;
private BigDecimal _decimalValue;
private boolean _booleanValue;
private float _floatValue;
private double _doubleValue;
private QName _qnameValue;
private GDate _gdateValue;
private GDuration _gdurationValue;
private byte[] _byteArrayValue;
private List<Object> _listValue;
private List<SchemaType> _listTypes;
private void resetValues() {
_localAttribute = null;
_wildcardAttribute = null;
_stringValue = null;
_decimalValue = null;
_booleanValue = false;
_floatValue = 0;
_doubleValue = 0;
_qnameValue = null;
_gdateValue = null;
_gdurationValue = null;
_byteArrayValue = null;
_listValue = null;
_listTypes = null;
_unionType = null;
}
/**
* @return Returns the SchemaType of the current element.
* This can be different than getCurrentElement().getType() if xsi:type attribute is used.
* Null is returned if no schema type is available.
* For attribute types use {@link #getCurrentAttribute()}.getType().
* Warning: the returned SchemaType can be an {@link org.apache.xmlbeans.XmlBeans#NO_TYPE},
* see {@link SchemaType#isNoType}. Or can be the parent type, for unrecognized elements
* that are part of wildcards.
*/
public SchemaType getCurrentElementSchemaType() {
State state = topState();
if (state != null) {
return state._type;
}
return null;
}
/**
* @return Returns the curent local element, null if one is not available, see {@link #getCurrentWildcardElement()}.
*/
public SchemaLocalElement getCurrentElement() {
if (_localElement != null) {
return _localElement;
}
// it means the element is to be skiped and it doesn't have a known SchemaLocalElement
if (_eatContent > 0) {
return null;
}
//try getting it from the stack (this should happen after END)
if (_stateStack != null && _stateStack._field instanceof SchemaLocalElement) {
return (SchemaLocalElement) _stateStack._field;
}
return null;
}
/**
* @return Returns the current particle, if this is a wildcard particle {@link SchemaParticle#WILDCARD}
* method {@link #getCurrentElement()} might return null if wildcard's processContents is skip or lax.
*/
public SchemaParticle getCurrentWildcardElement() {
return _wildcardElement;
}
/**
* @return Returns the curent local attribute, global attribute if the current attribute is part of an
* attribute wildcard, or null if none is available.
*/
public SchemaLocalAttribute getCurrentAttribute() {
return _localAttribute;
}
/**
* @return Returns the attribute model for attributes if available, else null is returned.
*/
public SchemaAttributeModel getCurrentWildcardAttribute() {
return _wildcardAttribute;
}
public String getStringValue() {
return _stringValue;
}
public BigDecimal getDecimalValue() {
return _decimalValue;
}
public boolean getBooleanValue() {
return _booleanValue;
}
public float getFloatValue() {
return _floatValue;
}
public double getDoubleValue() {
return _doubleValue;
}
public QName getQNameValue() {
return _qnameValue;
}
public GDate getGDateValue() {
return _gdateValue;
}
public GDuration getGDurationValue() {
return _gdurationValue;
}
public byte[] getByteArrayValue() {
return _byteArrayValue;
}
public List<Object> getListValue() {
return _listValue;
}
public List<SchemaType> getListTypes() {
return _listTypes;
}
public SchemaType getUnionType() {
return _unionType;
}
}