org.apache.xmlbeans.impl.validator.ValidatingXMLStreamReader Maven / Gradle / Ivy
/* 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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy