| /* |
| * 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.axiom.util.stax; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.Writer; |
| |
| import javax.activation.DataHandler; |
| import javax.xml.stream.XMLStreamConstants; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| |
| import org.apache.axiom.ext.stax.CharacterDataReader; |
| import org.apache.axiom.ext.stax.DelegatingXMLStreamReader; |
| import org.apache.axiom.ext.stax.datahandler.DataHandlerProvider; |
| import org.apache.axiom.ext.stax.datahandler.DataHandlerReader; |
| import org.apache.axiom.util.activation.EmptyDataSource; |
| import org.apache.axiom.util.base64.Base64DecodingOutputStreamWriter; |
| import org.apache.axiom.util.blob.BlobDataSource; |
| import org.apache.axiom.util.blob.MemoryBlob; |
| import org.apache.axiom.util.blob.WritableBlob; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * Contains utility methods to work with {@link XMLStreamReader} objects, including the extension |
| * defined by {@link DataHandlerReader}. In addition to {@link DataHandlerReader} support, this |
| * class also provides support for the legacy extension mechanism described below. |
| * |
| * <h3>Legacy XMLStreamReader extensions for optimized base64 handling</h3> |
| * |
| * <p> |
| * {@link XMLStreamReader} instances supporting the legacy extension must conform to the following |
| * requirements: |
| * </p> |
| * <ol> |
| * <li>{@link XMLStreamReader#getProperty(String)} must return {@link Boolean#TRUE} for the |
| * property identified by {@link org.apache.axiom.om.OMConstants#IS_DATA_HANDLERS_AWARE}, |
| * regardless of the current event. The property is assumed to be immutable and its value must not |
| * change during the lifetime of the {@link XMLStreamReader} implementation.</li> |
| * <li> |
| * <p> |
| * If the {@link XMLStreamReader} wishes to expose base64 encoded content using a |
| * {@link javax.activation.DataHandler} object, it must do so using a single |
| * {@link XMLStreamConstants#CHARACTERS} event. |
| * </p> |
| * <p> |
| * To maintain compatibility with consumers that are unaware of the extensions described here, the |
| * implementation should make sure that {@link XMLStreamReader#getText()}, |
| * {@link XMLStreamReader#getTextStart()}, {@link XMLStreamReader#getTextLength()}, |
| * {@link XMLStreamReader#getTextCharacters()}, |
| * {@link XMLStreamReader#getTextCharacters(int, char[], int, int)} and |
| * {@link XMLStreamReader#getElementText()} behave as expected for this type of event, i.e. return |
| * the base64 representation of the binary content. |
| * </p> |
| * </li> |
| * <li>{@link XMLStreamReader#getProperty(String)} must return {@link Boolean#TRUE} for the |
| * property identified by {@link org.apache.axiom.om.OMConstants#IS_BINARY} if the current event is |
| * a {@link XMLStreamConstants#CHARACTERS} event representing base64 encoded binary content and for |
| * which a {@link javax.activation.DataHandler} is available. For all other events, the returned |
| * value must be {@link Boolean#FALSE}.</li> |
| * <li> |
| * <p> |
| * If for a given event, the implementation returned {@link Boolean#TRUE} for the |
| * {@link org.apache.axiom.om.OMConstants#IS_BINARY} property, then a call to |
| * {@link XMLStreamReader#getProperty(String)} with argument |
| * {@link org.apache.axiom.om.OMConstants#DATA_HANDLER} must return the corresponding |
| * {@link javax.activation.DataHandler} object. |
| * </p> |
| * <p> |
| * The {@link org.apache.axiom.om.OMConstants#DATA_HANDLER} property is undefined for any other type |
| * of event. This implies that the consumer of the {@link XMLStreamReader} must check the |
| * {@link org.apache.axiom.om.OMConstants#IS_BINARY} property before retrieving the |
| * {@link org.apache.axiom.om.OMConstants#DATA_HANDLER} property. |
| * </p> |
| * </li> |
| * </ol> |
| * The extension mechanism described here has been deprecated mainly because it doesn't support |
| * deferred loading of the binary content. |
| */ |
| public class XMLStreamReaderUtils { |
| // Legacy property names; should be removed in Axiom 1.3 |
| private static final String IS_BINARY = "Axiom.IsBinary"; |
| private static final String DATA_HANDLER = "Axiom.DataHandler"; |
| private static final String IS_DATA_HANDLERS_AWARE = "IsDatahandlersAwareParsing"; |
| |
| private static final Log log = LogFactory.getLog(XMLStreamReaderUtils.class); |
| |
| private XMLStreamReaderUtils() {} |
| |
| /** |
| * Get the {@link DataHandlerReader} extension for a given {@link XMLStreamReader}, if |
| * available. If the {@link XMLStreamReader} only supports the legacy extension (as described |
| * above), then this method will return a compatibility wrapper. Note that this wrapper doesn't |
| * support deferred loading of the binary content. |
| * |
| * @param reader |
| * the stream reader to get the {@link DataHandlerReader} extension from |
| * @return the implementation of the extension, or <code>null</code> if the |
| * {@link XMLStreamReader} doesn't expose base64 encoded binary content as |
| * {@link DataHandler} objects. |
| */ |
| public static DataHandlerReader getDataHandlerReader(final XMLStreamReader reader) { |
| try { |
| DataHandlerReader dhr = (DataHandlerReader)reader.getProperty( |
| DataHandlerReader.PROPERTY); |
| if (dhr != null) { |
| return dhr; |
| } |
| } catch (IllegalArgumentException ex) { |
| // Just continue |
| } |
| Boolean isDataHandlerAware; |
| try { |
| isDataHandlerAware = (Boolean)reader.getProperty(IS_DATA_HANDLERS_AWARE); |
| } catch (IllegalArgumentException ex) { |
| return null; |
| } |
| if (isDataHandlerAware != null && isDataHandlerAware.booleanValue()) { |
| return new DataHandlerReader() { |
| public boolean isBinary() { |
| return ((Boolean)reader.getProperty(IS_BINARY)).booleanValue(); |
| } |
| |
| public boolean isOptimized() { |
| // This is compatible with the old StAXBuilder implementation |
| return true; |
| } |
| |
| public boolean isDeferred() { |
| return false; |
| } |
| |
| public String getContentID() { |
| return null; |
| } |
| |
| public DataHandler getDataHandler() { |
| return (DataHandler)reader.getProperty(DATA_HANDLER); |
| } |
| |
| public DataHandlerProvider getDataHandlerProvider() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Helper method to implement {@link XMLStreamReader#getProperty(String)}. This method |
| * processed the properties defined by {@link DataHandlerReader#PROPERTY} and the legacy |
| * extension mechanism (as described above). It can therefore be used to make a |
| * {@link XMLStreamReader} implementation compatible with code that expects it to implement this |
| * legacy extension. |
| * |
| * @param extension |
| * the reference to the {@link DataHandlerReader} extension for the |
| * {@link XMLStreamReader} implementation |
| * @param propertyName |
| * the name of the property, as passed to the |
| * {@link XMLStreamReader#getProperty(String)} method |
| * @return the property value as specified by the {@link DataHandlerReader} or legacy extension; |
| * <code>null</code> if the property is not specified by any of these two extensions |
| */ |
| public static Object processGetProperty(DataHandlerReader extension, String propertyName) { |
| if (extension == null || propertyName == null) { |
| throw new IllegalArgumentException(); |
| } else if (propertyName.equals(DataHandlerReader.PROPERTY)) { |
| return extension; |
| } else if (propertyName.equals(IS_DATA_HANDLERS_AWARE)) { |
| return Boolean.TRUE; |
| } else if (propertyName.equals(IS_BINARY)) { |
| return Boolean.valueOf(extension.isBinary()); |
| } else if (propertyName.equals(DATA_HANDLER)) { |
| try { |
| return extension.getDataHandler(); |
| } catch (XMLStreamException ex) { |
| throw new RuntimeException(ex); |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Get a {@link DataHandler} for the binary data encoded in an element. The method supports |
| * base64 encoded character data as well as optimized binary data through the |
| * {@link DataHandlerReader} extension. |
| * <p> |
| * <em>Precondition</em>: the reader is on a {@link XMLStreamConstants#START_ELEMENT} |
| * <p> |
| * <em>Postcondition</em>: the reader is on the corresponding |
| * {@link XMLStreamConstants#END_ELEMENT} |
| * |
| * @param reader the stream to read the data from |
| * @return the binary data from the element |
| */ |
| public static DataHandler getDataHandlerFromElement(XMLStreamReader reader) |
| throws XMLStreamException { |
| |
| int event = reader.next(); |
| if (event == XMLStreamConstants.END_ELEMENT) { |
| // This means that the element is actually empty -> return empty DataHandler |
| return new DataHandler(new EmptyDataSource("application/octet-stream")); |
| } else if (event != XMLStreamConstants.CHARACTERS) { |
| throw new XMLStreamException("Expected a CHARACTER event"); |
| } |
| DataHandlerReader dhr = getDataHandlerReader(reader); |
| if (dhr != null && dhr.isBinary()) { |
| DataHandler dh = dhr.getDataHandler(); |
| reader.next(); |
| return dh; |
| } else { |
| WritableBlob blob = new MemoryBlob(); |
| Writer out = new Base64DecodingOutputStreamWriter(blob.getOutputStream()); |
| try { |
| writeTextTo(reader, out); |
| // Take into account that in non coalescing mode, there may be additional |
| // CHARACTERS events |
| loop: while (true) { |
| switch (reader.next()) { |
| case XMLStreamConstants.CHARACTERS: |
| writeTextTo(reader, out); |
| break; |
| case XMLStreamConstants.END_ELEMENT: |
| break loop; |
| default: |
| throw new XMLStreamException("Expected a CHARACTER event"); |
| } |
| } |
| out.close(); |
| } catch (IOException ex) { |
| throw new XMLStreamException("Error during base64 decoding", ex); |
| } |
| return new DataHandler(new BlobDataSource(blob, "application/octet-string")); |
| } |
| } |
| |
| /** |
| * Get the character data for the current event from the given reader and |
| * write it to the given writer. The method will try to figure out the most |
| * efficient way to copy the data without unnecessary buffering or |
| * conversions between strings and character arrays. |
| * |
| * @param reader |
| * the reader to get the character data from |
| * @param writer |
| * the writer to write the character data to |
| * @throws XMLStreamException |
| * if the underlying XML source is not well-formed |
| * @throws IOException |
| * if an I/O error occurs when writing the character data |
| * @throws IllegalStateException |
| * if this state is not a valid text state. |
| * @see CharacterDataReader |
| */ |
| public static void writeTextTo(XMLStreamReader reader, Writer writer) throws XMLStreamException, IOException { |
| CharacterDataReader cdataReader; |
| try { |
| cdataReader = (CharacterDataReader)reader.getProperty(CharacterDataReader.PROPERTY); |
| } catch (IllegalArgumentException ex) { |
| cdataReader = null; |
| } |
| if (cdataReader != null) { |
| cdataReader.writeTextTo(writer); |
| } else { |
| writer.write(reader.getText()); |
| } |
| } |
| |
| /** |
| * Get the text content of the current element as a {@link Reader} object. |
| * |
| * @param reader |
| * The XML stream reader to read the element text from. The reader must be positioned |
| * on a {@link XMLStreamConstants#START_ELEMENT} event. |
| * @param allowNonTextChildren |
| * If set to <code>true</code>, non text child nodes are allowed and skipped. If set |
| * to <code>false</code> only text nodes are allowed and the presence of any other |
| * type of child node will trigger an exception. |
| * @return The reader from which the element text can be read. After the reader has reported the |
| * end of the stream, the XML stream reader will be positioned on the |
| * {@link XMLStreamConstants#END_ELEMENT} event corresponding to the initial |
| * {@link XMLStreamConstants#START_ELEMENT} event. Calling {@link Reader#close()} on the |
| * returned reader has no effect. Any parser exception will be reported by the reader |
| * using {@link XMLStreamIOException}. |
| * @throws IllegalStateException |
| * if the XML stream reader is not positioned on a |
| * {@link XMLStreamConstants#START_ELEMENT} event |
| */ |
| public static Reader getElementTextAsStream(XMLStreamReader reader, |
| boolean allowNonTextChildren) { |
| if (reader.getEventType() != XMLStreamReader.START_ELEMENT) { |
| throw new IllegalStateException("Reader must be on a START_ELEMENT event"); |
| } |
| return new TextFromElementReader(reader, allowNonTextChildren); |
| } |
| |
| /** |
| * Searches the wrapper and delegate classes to find the original {@link XMLStreamReader}. |
| * This method should only be used when a consumer of Axiom really needs to |
| * access the original stream reader. |
| * @param parser XMLStreamReader used by Axiom |
| * @return original parser |
| */ |
| public static XMLStreamReader getOriginalXMLStreamReader(XMLStreamReader parser) { |
| if (log.isDebugEnabled()) { |
| String clsName = (parser != null) ? parser.getClass().toString() : "null"; |
| log.debug("Entry getOriginalXMLStreamReader: " + clsName); |
| } |
| while (parser instanceof DelegatingXMLStreamReader) { |
| parser = ((DelegatingXMLStreamReader) parser).getParent(); |
| if (log.isDebugEnabled()) { |
| String clsName = (parser != null) ? parser.getClass().toString() : "null"; |
| log.debug(" parent: " + clsName); |
| } |
| } |
| if (log.isDebugEnabled()) { |
| String clsName = (parser != null) ? parser.getClass().toString() : "null"; |
| log.debug("Exit getOriginalXMLStreamReader: " + clsName); |
| } |
| return parser; |
| } |
| } |