org.apache.xmlbeans.impl.validator.ValidatingXMLStreamReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-xmlbeans Show documentation
Show all versions of commons-xmlbeans Show documentation
The Apache Commons Codec package contains simple encoder and decoders for
various formats such as Base64 and Hexadecimal. In addition to these
widely used encoders and decoders, the codec package also maintains a
collection of phonetic encoding utilities.
The newest version!
/* 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.SchemaType;
import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.common.ValidatorListener;
import org.apache.xmlbeans.impl.common.XmlWhitespace;
import org.apache.xmlbeans.impl.common.QNameHelper;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.Location;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.StreamReaderDelegate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* This class is a wrapper over a generic XMLStreamReader that provides validation.
* There are 3 cases:
*
1) the XMLStreamReader represents a document, it contains only one element document
* - in this case the user schema type should be null or it should be a document SchemaType
*
2) the XMLStreamReader represents an xml-fragment (content only) - must have at least one user type or xsi:type
*
a) it has an xsi:type - if user schema type is available it has to be a base type of xsi:type
*
b) it doesn't have xsi:type - user must provide a schema type
* otherwise will error and will not do validation
*
3) the XMLStreamReader represents a global attribute - i.e. user schema type is null and only one attribute
*
*
* @author Cezar Andrei (cezar.andrei at bea.com)
* Date: Feb 13, 2004
*/
public class ValidatingXMLStreamReader
extends StreamReaderDelegate
implements XMLStreamReader
{
public static final String OPTION_ATTTRIBUTE_VALIDATION_COMPAT_MODE = "OPTION_ATTTRIBUTE_VALIDATION_COMPAT_MODE";
private static final String URI_XSI = "http://www.w3.org/2001/XMLSchema-instance";
private static final QName XSI_TYPE = new QName(URI_XSI, "type");
private static final QName XSI_NIL = new QName(URI_XSI, "nil");
private static final QName XSI_SL = new QName(URI_XSI, "schemaLocation");
private static final QName XSI_NSL = new QName(URI_XSI, "noNamespaceSchemaLocation");
private SchemaType _contentType;
private SchemaTypeLoader _stl;
private XmlOptions _options;
private Collection _errorListener;
protected Validator _validator;
private final ElementEventImpl _elemEvent;
private final AttributeEventImpl _attEvent;
private final SimpleEventImpl _simpleEvent;
private PackTextXmlStreamReader _packTextXmlStreamReader;
private int _state;
private final int STATE_FIRSTEVENT = 0;
private final int STATE_VALIDATING = 1;
private final int STATE_ATTBUFFERING = 2;
private final int STATE_ERROR = 3;
private List _attNamesList;
private List _attValuesList;
private SchemaType _xsiType;
private int _depth;
/**
* Default constructor. Use init(...) to set the params.
* See {@link #init}
*/
public ValidatingXMLStreamReader()
{
super();
_elemEvent = new ElementEventImpl();
_attEvent = new AttributeEventImpl();
_simpleEvent = new SimpleEventImpl();
_packTextXmlStreamReader = new PackTextXmlStreamReader();
}
/**
* Used in case of reusing the same ValidatinXMLStreamReader object
* @param xsr The stream to be validated
* @param startWithCurrentEvent Validation will start if true with the current event or if false with the next event in the stream
* @param contentType The schemaType of the content. This can be null for document and global Att validation
* @param stl SchemaTypeLoader context of validation
* @param options Validator options
* @param errorListener Errors and warnings listener
*/
public void init(XMLStreamReader xsr, boolean startWithCurrentEvent, SchemaType contentType,
SchemaTypeLoader stl, XmlOptions options, Collection errorListener)
{
_packTextXmlStreamReader.init(xsr);
// setParent(xsr);
setParent(_packTextXmlStreamReader);
_contentType = contentType;
_stl = stl;
_options = options;
_errorListener = errorListener;
// _elemEvent.setXMLStreamReader(xsr);
// _attEvent.setXMLStreamReader(xsr);
// _simpleEvent.setXMLStreamReader(xsr);
_elemEvent.setXMLStreamReader(_packTextXmlStreamReader);
_attEvent.setXMLStreamReader(_packTextXmlStreamReader);
_simpleEvent.setXMLStreamReader(_packTextXmlStreamReader);
_validator = null;
_state = STATE_FIRSTEVENT;
if (_attNamesList!=null)
{
_attNamesList.clear();
_attValuesList.clear();
}
_xsiType = null;
_depth = 0;
if (startWithCurrentEvent)
{
int evType = getEventType();
validate_event(evType);
}
}
private static class PackTextXmlStreamReader
extends StreamReaderDelegate
implements XMLStreamReader
{
private boolean _hasBufferedText;
private StringBuffer _buffer = new StringBuffer();
private int _textEventType;
void init(XMLStreamReader xmlstream)
{
setParent(xmlstream);
_hasBufferedText = false;
_buffer.delete(0, _buffer.length());
}
public int next()
throws XMLStreamException
{
if (_hasBufferedText)
{
clearBuffer();
return super.getEventType();
}
int evType = super.next();
if (evType == XMLEvent.CHARACTERS || evType == XMLEvent.CDATA || evType == XMLEvent.SPACE)
{
_textEventType = evType;
bufferText();
}
return evType;
}
private void clearBuffer()
{
_buffer.delete(0, _buffer.length());
_hasBufferedText = false;
}
private void bufferText()
throws XMLStreamException
{
if (super.hasText())
_buffer.append( super.getText());
_hasBufferedText = true;
while (hasNext())
{
int evType = super.next();
switch (evType)
{
case XMLEvent.CHARACTERS:
case XMLEvent.CDATA:
case XMLEvent.SPACE:
if (super.hasText())
_buffer.append(super.getText());
case XMLEvent.COMMENT:
//ignore
continue;
default:
return;
}
}
}
public String getText()
{
assert _hasBufferedText;
return _buffer.toString();
}
public int getTextLength()
{
assert _hasBufferedText;
return _buffer.length();
}
public int getTextStart()
{
assert _hasBufferedText;
return 0;
}
public char[] getTextCharacters()
{
assert _hasBufferedText;
return _buffer.toString().toCharArray();
}
public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length)
{
assert _hasBufferedText;
_buffer.getChars(sourceStart, sourceStart + length, target, targetStart);
return length;
}
public boolean isWhiteSpace()
{
assert _hasBufferedText;
return XmlWhitespace.isAllSpace(_buffer);
}
public boolean hasText()
{
if (_hasBufferedText)
return true;
else
return super.hasText();
}
public int getEventType()
{
if (_hasBufferedText)
return _textEventType;
else
return super.getEventType();
}
}
private static class ElementEventImpl
implements ValidatorListener.Event
{
private static final int BUF_LENGTH = 1024;
private char[] _buf = new char[BUF_LENGTH];
private int _length;
private boolean _supportForGetTextCharacters = true;
private XMLStreamReader _xmlStream;
private void setXMLStreamReader(XMLStreamReader xsr)
{
_xmlStream = xsr;
}
// can return null, used only to locate errors
public XmlCursor getLocationAsCursor()
{
return null;
}
public javax.xml.stream.Location getLocation()
{
return _xmlStream.getLocation();
}
// fill up chars with the xsi:type attribute value if there is one othervise return false
public String getXsiType() // BEGIN xsi:type
{
return _xmlStream.getAttributeValue(URI_XSI, "type");
}
// fill up chars with xsi:nill attribute value if any
public String getXsiNil() // BEGIN xsi:nil
{
return _xmlStream.getAttributeValue(URI_XSI, "nil");
}
// not used curently
public String getXsiLoc() // BEGIN xsi:schemaLocation
{
return _xmlStream.getAttributeValue(URI_XSI, "schemaLocation");
}
// not used curently
public String getXsiNoLoc() // BEGIN xsi:noNamespaceSchemaLocation
{
return _xmlStream.getAttributeValue(URI_XSI, "noNamespaceSchemaLocation");
}
// On START and ATTR
public QName getName()
{
// avoid construction of a new QName object after the bug in getName() is fixed.
if (_xmlStream.hasName())
return new QName(_xmlStream.getNamespaceURI(), _xmlStream.getLocalName());
else
return null;
}
// On TEXT and ATTR
public String getText()
{
_length = 0;
addTextToBuffer();
return new String( _buf, 0, _length );
// return _xmlStream.getText();
}
public String getText(int wsr)
{
return XmlWhitespace.collapse( _xmlStream.getText(), wsr );
}
public boolean textIsWhitespace()
{
return _xmlStream.isWhiteSpace();
}
public String getNamespaceForPrefix(String prefix)
{
return _xmlStream.getNamespaceURI(prefix);
}
private void addTextToBuffer()
{
int textLength = _xmlStream.getTextLength();
ensureBufferLength(textLength);
if (_supportForGetTextCharacters)
try
{
_length = _xmlStream.getTextCharacters(0, _buf, _length, textLength);
}
catch(Exception e)
{
_supportForGetTextCharacters = false;
}
if(!_supportForGetTextCharacters)
{
System.arraycopy(_xmlStream.getTextCharacters(), _xmlStream.getTextStart(), _buf, _length, textLength);
_length = _length + textLength;
}
}
private void ensureBufferLength(int lengthToAdd)
{
if (_length + lengthToAdd>_buf.length)
{
char[] newBuf = new char[_length + lengthToAdd];
if (_length>0)
System.arraycopy(_buf, 0, newBuf, 0, _length);
_buf = newBuf;
}
}
}
private static final class AttributeEventImpl
implements ValidatorListener.Event
{
private int _attIndex;
private XMLStreamReader _xmlStream;
private void setXMLStreamReader(XMLStreamReader xsr)
{
_xmlStream = xsr;
}
// can return null, used only to locate errors
public XmlCursor getLocationAsCursor()
{
return null;
}
public javax.xml.stream.Location getLocation()
{
return _xmlStream.getLocation();
}
// fill up chars with the xsi:type attribute value if there is one othervise return false
public String getXsiType() // BEGIN xsi:type
{
throw new IllegalStateException();
}
// fill up chars with xsi:nill attribute value if any
public String getXsiNil() // BEGIN xsi:nil
{
throw new IllegalStateException();
}
// not used curently
public String getXsiLoc() // BEGIN xsi:schemaLocation
{
throw new IllegalStateException();
}
// not used curently
public String getXsiNoLoc() // BEGIN xsi:noNamespaceSchemaLocation
{
throw new IllegalStateException();
}
// On START and ATTR
public QName getName()
{
assert _xmlStream.isStartElement() : "Not on Start Element.";
String uri = _xmlStream.getAttributeNamespace(_attIndex);
QName qn = new QName(uri==null ? "" : uri, _xmlStream.getAttributeLocalName(_attIndex));
//System.out.println(" Att QName: " + qn);
return qn;
}
// On TEXT and ATTR
public String getText()
{
assert _xmlStream.isStartElement() : "Not on Start Element.";
return _xmlStream.getAttributeValue(_attIndex);
}
public String getText(int wsr)
{
assert _xmlStream.isStartElement() : "Not on Start Element.";
return XmlWhitespace.collapse( _xmlStream.getAttributeValue(_attIndex), wsr );
}
public boolean textIsWhitespace()
{
throw new IllegalStateException();
}
public String getNamespaceForPrefix(String prefix)
{
assert _xmlStream.isStartElement() : "Not on Start Element.";
return _xmlStream.getNamespaceURI(prefix);
}
private void setAttributeIndex(int attIndex)
{
_attIndex = attIndex;
}
}
/**
* This is used as implementation of Event for validating global attributes
* and for pushing the buffered attributes
*/
private static final class SimpleEventImpl
implements ValidatorListener.Event
{
private String _text;
private QName _qname;
private XMLStreamReader _xmlStream;
private void setXMLStreamReader(XMLStreamReader xsr)
{
_xmlStream = xsr;
}
// should return null, getLocation will be used, used only to locate errors
public XmlCursor getLocationAsCursor()
{ return null; }
public javax.xml.stream.Location getLocation()
{
return _xmlStream.getLocation();
}
// fill up chars with the xsi:type attribute value if there is one othervise return false
public String getXsiType() // BEGIN xsi:type
{ return null; }
// fill up chars with xsi:nill attribute value if any
public String getXsiNil() // BEGIN xsi:nil
{ return null; }
// not used curently
public String getXsiLoc() // BEGIN xsi:schemaLocation
{ return null; }
// not used curently
public String getXsiNoLoc() // BEGIN xsi:noNamespaceSchemaLocation
{ return null; }
// On START and ATTR
public QName getName()
{ return _qname; }
// On TEXT and ATTR
public String getText()
{
return _text;
}
public String getText(int wsr)
{
return XmlWhitespace.collapse( _text, wsr );
}
public boolean textIsWhitespace()
{ return false; }
public String getNamespaceForPrefix(String prefix)
{
return _xmlStream.getNamespaceURI(prefix);
}
}
/* public methods in XMLStreamReader */
public Object getProperty(String s) throws IllegalArgumentException
{
return super.getProperty(s);
}
public int next() throws XMLStreamException
{
int evType = super.next();
//debugEvent(evType);
validate_event(evType);
return evType;
}
private void validate_event(int evType)
{
if (_state==STATE_ERROR)
return;
if (_depth<0)
throw new IllegalArgumentException("ValidatingXMLStreamReader cannot go further than the subtree is was initialized on.");
switch(evType)
{
case XMLEvent.START_ELEMENT:
_depth++;
if (_state == STATE_ATTBUFFERING)
pushBufferedAttributes();
if (_validator==null)
{
// avoid construction of a new QName object after the bug in getName() is fixed.
QName qname = new QName(getNamespaceURI(), getLocalName());
if (_contentType==null)
_contentType = typeForGlobalElement(qname);
if (_state==STATE_ERROR)
break;
initValidator(_contentType);
_validator.nextEvent(Validator.BEGIN, _elemEvent);
}
_validator.nextEvent(Validator.BEGIN, _elemEvent);
int attCount = getAttributeCount();
validate_attributes(attCount);
break;
case XMLEvent.ATTRIBUTE:
if (getAttributeCount()==0)
break;
if (_state == STATE_FIRSTEVENT || _state == STATE_ATTBUFFERING)
{
// buffer all Attributes
for (int i=0; i 1 than the validator will add an error
}
else
{
addError("No content type provided for validation of a content model.");
_state = STATE_ERROR;
return;
}
}
// here validationType is the right type, start pushing all acumulated attributes
initValidator(validationType);
_validator.nextEvent(Validator.BEGIN, _simpleEvent);
// validate attributes from _attNamesList
validate_attributes(_attNamesList.size());
_attNamesList = null;
_attValuesList = null;
_state = STATE_VALIDATING;
}
private boolean isSpecialAttribute(QName qn)
{
if (qn.getNamespaceURI().equals(URI_XSI))
return qn.getLocalPart().equals(XSI_TYPE.getLocalPart()) ||
qn.getLocalPart().equals(XSI_NIL.getLocalPart()) ||
qn.getLocalPart().equals(XSI_SL.getLocalPart()) ||
qn.getLocalPart().equals(XSI_NSL.getLocalPart());
return false;
}
/**
* Initializes the validator for the given schemaType
* @param schemaType
*/
private void initValidator(SchemaType schemaType)
{
assert schemaType!=null;
_validator = new Validator(schemaType, null, _stl, _options, _errorListener);
}
private SchemaType typeForGlobalElement(QName qname)
{
assert qname!=null;
SchemaType docType = _stl.findDocumentType(qname);
if (docType==null)
{
addError("Schema document type not found for element '" + qname + "'.");
_state = STATE_ERROR;
}
return docType;
}
private void addError(String msg)
{
String source = null;
Location location = getLocation();
if (location != null)
{
source = location.getPublicId();
if (source==null)
source = location.getSystemId();
_errorListener.add(XmlError.forLocation(msg, source, location));
}
else
_errorListener.add(XmlError.forMessage(msg));
}
protected void validate_attributes(int attCount)
{
for(int i=0; i