/*
* 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 org.apache.tuscany.sca.common.xml.stax;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.tuscany.sca.common.xml.dom.DOMHelper;
import org.apache.tuscany.sca.common.xml.stax.impl.StAX2SAXAdapter;
import org.apache.tuscany.sca.common.xml.stax.impl.XMLStreamSerializer;
import org.apache.tuscany.sca.common.xml.stax.reader.DOMXMLStreamReader;
import org.apache.tuscany.sca.core.ExtensionPointRegistry;
import org.apache.tuscany.sca.core.FactoryExtensionPoint;
import org.apache.tuscany.sca.core.UtilityExtensionPoint;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* Helper class for StAX
* @tuscany.spi.extension.asclient
*/
public class StAXHelper {
private final XMLInputFactory inputFactory;
private final XMLOutputFactory outputFactory;
private final DOMHelper domHelper;
public StAXHelper(ExtensionPointRegistry registry) {
FactoryExtensionPoint factories = registry.getExtensionPoint(FactoryExtensionPoint.class);
factories.getFactory(XMLInputFactory.class);
inputFactory = factories.getFactory(XMLInputFactory.class);
outputFactory = factories.getFactory(XMLOutputFactory.class);
outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class);
domHelper = utilities.getUtility(DOMHelper.class);
}
public static StAXHelper getInstance(ExtensionPointRegistry registry) {
UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class);
return utilities.getUtility(StAXHelper.class);
}
/**
* @param inputFactory
* @param outputFactory
* @param domHelper
*/
public StAXHelper(XMLInputFactory inputFactory, XMLOutputFactory outputFactory, DOMHelper domHelper) {
super();
this.inputFactory = inputFactory;
this.outputFactory = outputFactory;
if (outputFactory != null) {
this.outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
}
this.domHelper = domHelper;
}
public XMLStreamReader createXMLStreamReader(InputStream inputStream) throws XMLStreamException {
return inputFactory.createXMLStreamReader(inputStream);
}
public XMLStreamReader createXMLStreamReader(Reader reader) throws XMLStreamException {
return inputFactory.createXMLStreamReader(reader);
}
public XMLStreamReader createXMLStreamReader(Source source) throws XMLStreamException {
return inputFactory.createXMLStreamReader(source);
}
public XMLStreamReader createXMLStreamReader(Node node) throws XMLStreamException {
/*
// DOMSource is not supported by the XMLInputFactory from JDK 6
DOMSource source = new DOMSource(node);
return createXMLStreamReader(source);
*/
return new DOMXMLStreamReader(node);
}
public XMLStreamReader createXMLStreamReader(String string) throws XMLStreamException {
StringReader reader = new StringReader(string);
return createXMLStreamReader(reader);
}
private static InputStream openStream(URL url) throws IOException {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5041014
connection.setUseCaches(false);
}
InputStream is = connection.getInputStream();
return is;
}
public XMLStreamReader createXMLStreamReader(URL url) throws XMLStreamException {
try {
// Set up a StreamSource from the url, since this has an associated URL that
// can be used by the parser to find references to other files such as DTDs
StreamSource scdlSource = new StreamSource( openStream(url), url.toString() );
return inputFactory.createXMLStreamReader(scdlSource);
} catch (IOException e) {
throw new XMLStreamException(e);
}
}
public String saveAsString(XMLStreamReader reader) throws XMLStreamException {
StringWriter writer = new StringWriter();
save(reader, writer);
return writer.toString();
}
public void save(XMLStreamReader reader, OutputStream outputStream) throws XMLStreamException {
XMLStreamWriter streamWriter = createXMLStreamWriter(outputStream);
save(reader, streamWriter);
}
public XMLStreamWriter createXMLStreamWriter(OutputStream outputStream) throws XMLStreamException {
return outputFactory.createXMLStreamWriter(outputStream);
}
public void save(XMLStreamReader reader, Writer writer) throws XMLStreamException {
XMLStreamWriter streamWriter = createXMLStreamWriter(writer);
save(reader, streamWriter);
}
public XMLStreamWriter createXMLStreamWriter(Writer writer) throws XMLStreamException {
return outputFactory.createXMLStreamWriter(writer);
}
public Node saveAsNode(XMLStreamReader reader) throws XMLStreamException {
// woodstox 3.2.4 fails due to http://jira.codehaus.org/browse/WSTX-144
// this issue has been fixed in woodstox 3.2.9
// We can use the commented code once we move to woodstox 3.2.9
/*
XMLStreamSerializer serializer = new XMLStreamSerializer();
Document document = domHelper.newDocument();
DOMResult result = new DOMResult(document);
XMLStreamWriter streamWriter = createXMLStreamWriter(result);
serializer.serialize(reader, streamWriter);
streamWriter.flush();
return result.getNode();
*/
Document root = domHelper.newDocument();
ContentHandler handler = domHelper.createContentHandler(root);
try {
saveAsSAX(reader, handler);
} catch (SAXException e) {
throw new XMLStreamException(e);
}
return root;
}
public XMLStreamWriter createXMLStreamWriter(Result result) throws XMLStreamException {
return outputFactory.createXMLStreamWriter(result);
}
public void save(XMLStreamReader reader, XMLStreamWriter writer) throws XMLStreamException {
XMLStreamSerializer serializer = new XMLStreamSerializer(isReparingNamespaces());
serializer.serialize(reader, writer);
writer.flush();
}
public void saveAsSAX(XMLStreamReader reader, ContentHandler contentHandler) throws XMLStreamException,
SAXException {
new StAX2SAXAdapter(false).parse(reader, contentHandler);
}
/**
* @param url
* @param element
* @param attribute
* @param rootOnly
* @return
* @throws IOException
* @throws XMLStreamException
*/
public String readAttribute(URL url, QName element, String attribute) throws IOException, XMLStreamException {
if (attribute == null) {
attribute = "targetNamespace";
}
XMLStreamReader reader = createXMLStreamReader(url);
try {
return readAttributeFromRoot(reader, element, attribute);
} finally {
reader.close();
}
}
public List<String> readAttributes(URL url, QName element, String attribute) throws IOException, XMLStreamException {
if (attribute == null) {
attribute = "targetNamespace";
}
XMLStreamReader reader = createXMLStreamReader(url);
try {
Attribute attr = new Attribute(element, attribute);
return readAttributes(reader, attr)[0].getValues();
} finally {
reader.close();
}
}
/**
* Returns the boolean value of an attribute.
* @param reader
* @param name
* @return
*/
public static Boolean getAttributeAsBoolean(XMLStreamReader reader, String name) {
String value = reader.getAttributeValue(null, name);
if (value == null) {
return null;
}
return Boolean.valueOf(value);
}
/**
* Returns the QName value of an attribute.
* @param reader
* @param name
* @return
*/
public static QName getAttributeAsQName(XMLStreamReader reader, String name) {
String qname = reader.getAttributeValue(null, name);
return getValueAsQName(reader, qname);
}
/**
* Returns the value of an attribute as a list of QNames.
* @param reader
* @param name
* @return
*/
public static List<QName> getAttributeAsQNames(XMLStreamReader reader, String name) {
String value = reader.getAttributeValue(null, name);
if (value != null) {
List<QName> qnames = new ArrayList<QName>();
for (StringTokenizer tokens = new StringTokenizer(value); tokens.hasMoreTokens();) {
qnames.add(getValueAsQName(reader, tokens.nextToken()));
}
return qnames;
} else {
return Collections.emptyList();
}
}
/**
* Returns a QName from a string.
* @param reader
* @param value
* @return
*/
public static QName getValueAsQName(XMLStreamReader reader, String value) {
if (value != null) {
int index = value.indexOf(':');
String prefix = index == -1 ? "" : value.substring(0, index);
String localName = index == -1 ? value : value.substring(index + 1);
String ns = reader.getNamespaceContext().getNamespaceURI(prefix);
if (ns == null) {
ns = "";
}
return new QName(ns, localName, prefix);
} else {
return null;
}
}
/**
* Returns the string value of an attribute.
* @param reader
* @param name
* @return
*/
public static String getAttributeAsString(XMLStreamReader reader, String name) {
return reader.getAttributeValue(null, name);
}
/**
* TUSCANY-242
*
* Returns the URI value of an attribute as a string and first applies the
* URI whitespace processing as defined in section 4.3.6 of XML Schema Part2: Datatypes
* [http://www.w3.org/TR/xmlschema-2/#rf-facets]. anyURI is defined with the following
* XSD:
* <xs:simpleType name="anyURI" id="anyURI">
* <xs:restriction base="xs:anySimpleType">
* <xs:whiteSpace value="collapse" fixed="true" id="anyURI.whiteSpace"/>
* </xs:restriction>
* </xs:simpleType>
*
* The <xs:whiteSpace value="collapse"/> constraining facet is defined as follows
*
* replace
* All occurrences of #x9 (tab), #xA (line feed) and #xD (carriage return) are replaced with #x20 (space)
* collapse
* After the processing implied by replace, contiguous sequences of #x20's are collapsed to a single #x20,
* and leading and trailing #x20's are removed
*
* It seems that the StAX parser does apply this rule so we do it here.
*
* @param reader
* @param name
* @return
*/
public static String getAttributeAsURIString(XMLStreamReader reader, String name) {
// get the basic string value
String uri = reader.getAttributeValue(null, name);
// apply the "collapse" rule
if (uri != null){
// turn tabs, line feeds and carriage returns into spaces
uri = uri.replace('\t', ' ');
uri = uri.replace('\n', ' ');
uri = uri.replace('\r', ' ');
// remote leading and trailing spaces. Other whitespace
// has already been converted to spaces above
uri = uri.trim();
// collapse any contiguous spaces into a single space
StringBuilder sb= new StringBuilder(uri.length());
boolean spaceFound= false;
for(int i=0; i< uri.length(); ++i){
char c= uri.charAt(i);
if(c == ' '){
if(!spaceFound){
sb.append(c);
spaceFound = true;
} else {
// collapse the space by ignoring it
}
}else{
sb.append(c);
spaceFound= false;
}
}
uri = sb.toString();
}
return uri;
}
/**
* Returns the value of xsi:type attribute
* @param reader The XML stream reader
* @return The QName of the type, if the attribute is not present, null is
* returned.
*/
public static QName getXSIType(XMLStreamReader reader) {
String qname = reader.getAttributeValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type");
return getValueAsQName(reader, qname);
}
/**
* Test if an attribute is explicitly set
* @param reader
* @param name
* @return
*/
public static boolean isAttributePresent(XMLStreamReader reader, String name) {
return reader.getAttributeValue(null, name) != null;
}
/**
* Advance the stream to the next END_ELEMENT event skipping any nested
* content.
* @param reader the reader to advance
* @throws XMLStreamException if there was a problem reading the stream
*/
public static void skipToEndElement(XMLStreamReader reader) throws XMLStreamException {
int depth = 0;
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
depth++;
} else if (event == XMLStreamConstants.END_ELEMENT) {
if (depth == 0) {
return;
}
depth--;
}
}
}
private Attribute[] readAttributes(XMLStreamReader reader, AttributeFilter filter) throws XMLStreamException {
XMLStreamReader newReader = inputFactory.createFilteredReader(reader, filter);
while (filter.proceed() && newReader.hasNext()) {
newReader.next();
}
return filter.attributes;
}
public Attribute[] readAttributes(URL url, Attribute... attributes) throws XMLStreamException {
XMLStreamReader reader = createXMLStreamReader(url);
try {
return readAttributes(reader, attributes);
} finally {
reader.close();
}
}
public Attribute[] readAttributes(XMLStreamReader reader, Attribute... attributes) throws XMLStreamException {
return readAttributes(reader, new AttributeFilter(false, attributes));
}
private String readAttributeFromRoot(XMLStreamReader reader, Attribute filter) throws XMLStreamException {
Attribute[] attrs = readAttributes(reader, new AttributeFilter(true, filter));
List<String> values = attrs[0].getValues();
if (values.isEmpty()) {
return null;
} else {
return values.get(0);
}
}
public String readAttributeFromRoot(XMLStreamReader reader, QName element, String attributeName)
throws XMLStreamException {
Attribute filter = new Attribute(element, attributeName);
return readAttributeFromRoot(reader, filter);
}
/**
*
* @tuscany.spi.extension.asclient
*
*/
public static class Attribute {
private QName element;
private String name;
private List<String> values = new ArrayList<String>();
/**
* @param element
* @param name
*/
public Attribute(QName element, String name) {
super();
this.element = element;
this.name = name;
}
public List<String> getValues() {
return values;
}
}
private static class AttributeFilter implements StreamFilter {
private boolean proceed = true;
private Attribute[] attributes;
private boolean rootOnly;
/**
* @param rootOnly
*/
public AttributeFilter(boolean rootOnly, Attribute... attributes) {
super();
this.rootOnly = rootOnly;
this.attributes = attributes;
}
public boolean accept(XMLStreamReader reader) {
if (attributes == null || attributes.length == 0) {
proceed = false;
return true;
}
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
QName name = reader.getName();
for (Attribute attr : attributes) {
if (attr.element.equals(name)) {
attr.values.add(reader.getAttributeValue(null, attr.name));
}
}
if (rootOnly) {
proceed = false;
}
}
return true;
}
public boolean proceed() {
return proceed;
}
}
public XMLInputFactory getInputFactory() {
return inputFactory;
}
private boolean isReparingNamespaces() {
if (outputFactory == null) {
return Boolean.TRUE;
}
return outputFactory.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES) == Boolean.TRUE;
}
public XMLOutputFactory getOutputFactory() {
return outputFactory;
}
public String writeAttribute(XMLStreamWriter writer, QName name, String value) throws XMLStreamException {
return writeAttribute(writer, name.getPrefix(), name.getLocalPart(), name.getNamespaceURI(), value);
}
public String writeAttribute(XMLStreamWriter writer,
String prefix,
String localName,
String namespaceURI,
String value) throws XMLStreamException {
if (value == null) {
return null;
}
XMLStreamSerializer serializer = new XMLStreamSerializer(isReparingNamespaces());
return serializer.writeAttribute(writer, prefix, localName, namespaceURI, value);
}
public void writeStartElement(XMLStreamWriter writer, QName name) throws XMLStreamException {
writeStartElement(writer, name.getPrefix(), name.getLocalPart(), name.getNamespaceURI());
}
public void writeStartElement(XMLStreamWriter writer, String prefix, String localName, String namespaceURI)
throws XMLStreamException {
XMLStreamSerializer serializer = new XMLStreamSerializer(isReparingNamespaces());
serializer.writeStartElement(writer, prefix, localName, namespaceURI);
}
public String writeNamespace(XMLStreamWriter writer, String prefix, String namespaceURI) throws XMLStreamException {
XMLStreamSerializer serializer = new XMLStreamSerializer(isReparingNamespaces());
return serializer.writeNamespace(writer, prefix, namespaceURI);
}
}