All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.base.wbxml.WBXMLReader Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Core, http://www.openmdx.org/
 * Description: WBXML Reader 
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2010-2013, OMEX AG, Switzerland
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * ------------------
 * 
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.base.wbxml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.Deque;

import javax.xml.XMLConstants;

import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.text.Charsets;
import org.openmdx.base.xml.spi.FeatureSupport;
import org.openmdx.base.xml.spi.LargeObjectWriter;
import org.openmdx.kernel.exception.BasicException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;

/**
 * WBXML Reader
 */
public class WBXMLReader implements XMLReader, FeatureSupport {

    /**
     * Constructor
     * 

* The plug-in is *

    *
  • either set via setProperty({@link WBXMLPlugIns#PLUG_IN}, plugIn) *
  • or PUBLIC id based (per document) *
*/ public WBXMLReader() { this.plugIn = null; } /** * Constructor * * @param plugIn * the {@link WBXMLPlugIns#PLUG_IN PlugIn} property */ public WBXMLReader( PlugIn plugIn ) { this.plugIn = plugIn; } /** * The Exhaust feature */ public static final String EXHAUST = "http://openmdx.org/wbxml/features/exhaust"; /** * */ private ContentHandler contentHandler; /** * */ private ErrorHandler errorHandler; /** * The {@link WBXMLPlugIns#PLUG_IN} property value */ private PlugIn plugIn; /** * The buffer for readStringI() */ private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); /** * The element queue */ private final Deque stack = new ArrayDeque(); /** * */ private InputStream in; /** * */ private Locator locator; /** * The curren attribute page, initially 0 */ private int attributePage; /** * The current tag page, initially 0 */ private int tagPage; /** * The character encoding */ private String charset; /** * Character Data Buffer */ private char[] characters = new char[256]; /** * Read to the end of the input file if true */ private boolean exhaust = true; /** * Determine the plug-in to be used for this document * * @param publicDocumentId * * @return the plug-in to be used for this document * @throws ServiceException */ private PlugIn getPlugIn( String publicDocumentId ) throws ServiceException { if (this.plugIn == null) { if (publicDocumentId != null) { return WBXMLPlugIns.newInstance(publicDocumentId); } throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE, "A PUBLIC document id must be provided unless a WBXML plug-in has been configured" ); } else { this.plugIn.reset(); return this.plugIn; } } /** * Create a string source reading the content from the given input stream * * @param charset * @param in * @param byteCount * * @return a new string source * * @throws IOException */ protected StringSource newStringSource( Charset charset, InputStream in, int byteCount ) throws IOException { return StandardStringSource.isSupported(charset) ? new StandardStringSource( charset, in, byteCount ) : new GenericStringSource( charset, in, byteCount ); } /** * Parse the binary stream * * @param in * the stream to be parsed * @throws SAXException * @throws IOException */ private void parse( InputStream in ) throws IOException,SAXException { this.tagPage = 0; this.attributePage = 0; this.in = in; this.stack.clear(); try { // // Version // int version = readByte(); if (version != Version.SPECIFICATION_VERSION_ID) { throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.NOT_SUPPORTED, "Unsupported WBXML Version", new BasicException.Parameter("supported", Version.SPECIFICATION_VERSION), new BasicException.Parameter("requested", Version.toString(version)) ); } // // Document type // final int publicid = readInt(); final String publicDocumentId = publicid == ExternalIdentifiers.LITERAL ? readStringFromTable() : ExternalIdentifiers.toPublicDocumentId(publicid); this.plugIn = getPlugIn(publicDocumentId); // // Character Encoding // int mibEnum = readInt(); this.charset = mibEnum == 0 ? "UTF-8" : Charsets.toCharsetName(mibEnum); // // String Table // int stringTableSize = readInt(); if (stringTableSize > 0) { this.plugIn.setStringTable( newStringSource( Charset.forName(this.charset), this.in, stringTableSize ) ); } // // Body // this.contentHandler.startDocument(); for (boolean epilog = false; !epilog;) { int id = this.in.read(); switch (id) { case -1: throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE, "Unclosed elements at end of file", new BasicException.Parameter("left", this.stack) ); case GlobalTokens.SWITCH_PAGE: this.tagPage = readByte(); break; case GlobalTokens.END: Object tag = this.stack.removeLast(); if (tag instanceof CodeResolution) { CodeResolution t = (CodeResolution) tag; this.contentHandler.endElement( t.namespaceURI, t.localName, t.name ); } else { this.contentHandler.endElement( XMLConstants.NULL_NS_URI, (String) tag, (String) tag ); } epilog = this.stack.isEmpty(); break; case GlobalTokens.ENTITY: this.characters[0] = (char) readInt(); this.contentHandler.characters(this.characters, 0, 1); break; case GlobalTokens.STR_I: { String s = readInlineString(); if (this.characters.length < s.length()) { int newLength = 2 * this.characters.length; while (newLength < s.length()) { newLength *= 2; } this.characters = new char[newLength]; } s.getChars(0, s.length(), characters, 0); this.contentHandler.characters(characters, 0, s.length()); break; } case GlobalTokens.EXT_I_0: case GlobalTokens.EXT_I_1: case GlobalTokens.EXT_I_2: case GlobalTokens.EXT_T_0: case GlobalTokens.EXT_T_1: case GlobalTokens.EXT_T_2: case GlobalTokens.EXT_0: case GlobalTokens.EXT_1: case GlobalTokens.EXT_2: case GlobalTokens.OPAQUE: handleExtensions(id); break; case GlobalTokens.PI: readProcessingInstruction(); break; case GlobalTokens.STR_T: { String s = readStringFromTable(); if (this.characters.length < s.length()) { int newLength = 2 * this.characters.length; while (newLength < s.length()) { newLength *= 2; } this.characters = new char[newLength]; } s.getChars(0, s.length(), this.characters, 0); this.contentHandler.characters(this.characters, 0, s.length()); break; } default: Object resolvedTag = this.plugIn.resolveTag(this.tagPage, (id & 0x03f)); if (resolvedTag instanceof CodeResolution) { this.readElement( (CodeResolution) resolvedTag, (id & GlobalTokens.ATTRIBUTE_FLAG) != 0, (id & GlobalTokens.CONTENT_FLAG) != 0 ); } else { this.readElement( (String) resolvedTag, (id & GlobalTokens.ATTRIBUTE_FLAG) != 0, (id & GlobalTokens.CONTENT_FLAG) != 0 ); } epilog = this.stack.isEmpty(); } } if (this.exhaust) { for (int id = this.in.read(); id >= 0; id = this.in.read()) { if (id == GlobalTokens.PI) { this.readProcessingInstruction(); } else { throw new ServiceException( BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE, "Only processing instructions are allowed in epilog", new BasicException.Parameter("expected", GlobalTokens.PI), new BasicException.Parameter("actual", id) ); } } } this.contentHandler.endDocument(); } catch (ServiceException exception) { BasicException cause = exception.getCause(); throw new SAXParseException( cause.getDescription(), this.locator, cause ); } } // -------------- internal methods start here -------------------- private void handleExtensions( int id ) throws ServiceException,IOException,SAXException { switch (id) { case GlobalTokens.EXT_I_0: this.plugIn.ext0(readInlineString()); break; case GlobalTokens.EXT_I_1: this.plugIn.ext1(readInlineString()); break; case GlobalTokens.EXT_I_2: this.plugIn.ext2(readInlineString()); break; case GlobalTokens.EXT_T_0: this.plugIn.ext0(readInt()); break; case GlobalTokens.EXT_T_1: this.plugIn.ext1(readInt()); break; case GlobalTokens.EXT_T_2: this.plugIn.ext2(readInt()); break; case GlobalTokens.EXT_0: this.plugIn.ext0(); break; case GlobalTokens.EXT_1: this.plugIn.ext1(); break; case GlobalTokens.EXT_2: this.plugIn.ext2(); break; case GlobalTokens.OPAQUE: { int len = readInt(); byte[] buf = new byte[len]; for (int i = 0; i < len; i++) { buf[i] = (byte) readByte(); } if (this.contentHandler instanceof LargeObjectWriter) { ((LargeObjectWriter) this.contentHandler).writeBinaryData(buf, 0, len); } else { this.plugIn.opaque(buf); } } // case OPAQUE } // SWITCH } /** * Read a processing instruction * * @throws ServiceException * @throws SAXException * @throws IOException */ private void readProcessingInstruction() throws IOException,SAXException,ServiceException { Attributes processingInstruction = readAttributes(); this.contentHandler.processingInstruction( processingInstruction.getQName(0), processingInstruction.getValue(0) ); } /** * Read attributes or a processing instruction * * @return the attributes holder * * @throws IOException * @throws SAXException * @throws ServiceException */ private Attributes readAttributes() throws IOException,SAXException,ServiceException { AttributesImpl result = new AttributesImpl(); for (int id = readByte(); id != GlobalTokens.END;) { if (id == GlobalTokens.SWITCH_PAGE) { this.attributePage = readByte(); id = readByte(); } else { CodeResolution attribute = this.plugIn.resolveAttributeStart(this.attributePage, id); String value = attribute.valueStart; StringBuilder buffer = null; id = readByte(); while (id > 128 || id == GlobalTokens.ENTITY || id == GlobalTokens.STR_I || id == GlobalTokens.STR_T || (id >= GlobalTokens.EXT_I_0 && id <= GlobalTokens.EXT_I_2) || (id >= GlobalTokens.EXT_T_0 && id <= GlobalTokens.EXT_T_2)) { if (buffer == null) { buffer = new StringBuilder(value); } switch (id) { case GlobalTokens.ENTITY: buffer.append((char) readInt()); break; case GlobalTokens.STR_I: buffer.append(readInlineString()); break; case GlobalTokens.EXT_I_0: case GlobalTokens.EXT_I_1: case GlobalTokens.EXT_I_2: case GlobalTokens.EXT_T_0: case GlobalTokens.EXT_T_1: case GlobalTokens.EXT_T_2: case GlobalTokens.EXT_0: case GlobalTokens.EXT_1: case GlobalTokens.EXT_2: case GlobalTokens.OPAQUE: handleExtensions(id); break; case GlobalTokens.STR_T: buffer.append(readStringFromTable()); break; default: buffer.append(this.plugIn.resolveAttributeValue(this.attributePage, id)); } id = readByte(); } if (buffer != null) { value = buffer.toString(); } result.addAttribute(attribute.namespaceURI, attribute.localName, attribute.name, null, value); } } return result; } private void readElement( CodeResolution tag, boolean hasAttributes, boolean hasContent ) throws IOException,SAXException,ServiceException { this.contentHandler.startElement( tag.namespaceURI, tag.localName, tag.name, hasAttributes ? readAttributes() : new AttributesImpl() ); if (hasContent) { this.stack.add(tag); } else { this.contentHandler.endElement( tag.namespaceURI, tag.localName, tag.name ); } } private void readElement( String tag, boolean hasAttributes, boolean hasContent ) throws IOException,SAXException,ServiceException { contentHandler.startElement( XMLConstants.NULL_NS_URI, tag, tag, hasAttributes ? readAttributes() : new AttributesImpl() ); if (hasContent) { stack.add(tag); } else { contentHandler.endElement( XMLConstants.NULL_NS_URI, tag, tag ); } } int readByte() throws IOException,SAXException { int i = in.read(); if (i == -1) throw new SAXException("Unexpected EOF"); return i; } /** * Read a multi-byte integer value * * @return the Integer value * * @throws SAXException * @throws IOException */ private int readInt() throws SAXException,IOException { int result = 0; int i; do { i = readByte(); result <<= 7; result |= i & 0x7f; } while ((i & 0x80) != 0); return result; } /** * Read inline string * * @return the String value * * @throws IOException * @throws SAXException */ private String readInlineString() throws IOException,SAXException { this.buffer.reset(); while (true) { int i = in.read(); if (i == -1) throw new SAXException("Unexpected EOF"); if (i == 0) return buffer.toString(this.charset); buffer.write(i); } } /** * Read string from table * * @return the String value * * @throws IOException * @throws SAXException * @throws ServiceException */ private String readStringFromTable() throws IOException,SAXException,ServiceException { return this.plugIn.resolveString(readInt()).toString(); } //------------------------------------------------------------------------ // Implements XMLReader //------------------------------------------------------------------------ /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getContentHandler() */ @Override public ContentHandler getContentHandler() { return this.contentHandler; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getDTDHandler() */ @Override public DTDHandler getDTDHandler() { return null; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getEntityResolver() */ @Override public EntityResolver getEntityResolver() { return null; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getErrorHandler() */ @Override public ErrorHandler getErrorHandler() { return this.errorHandler; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getFeature(java.lang.String) */ @Override public boolean getFeature( String name ) throws SAXNotRecognizedException,SAXNotSupportedException { if (WBXMLReader.EXHAUST.equals(name)) { return this.exhaust; } else throw new SAXNotRecognizedException( "Feature: " + name ); } /* * (non-Javadoc) * * @see org.openmdx.base.xml.spi.FeatureSupport#isFeatureSupported(java.lang.String) */ @Override public boolean isFeatureSupported( String feature ) { return WBXMLReader.EXHAUST.equals(feature); } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#getProperty(java.lang.String) */ @Override public Object getProperty( String name ) throws SAXNotRecognizedException,SAXNotSupportedException { if (WBXMLPlugIns.PLUG_IN.equals(name)) { return this.plugIn; } else { throw new SAXNotRecognizedException("Property: " + name); } } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource) */ @Override public void parse( final InputSource input ) throws IOException,SAXException { this.locator = newLocator(input); if (input.getByteStream() != null) { parse(input.getByteStream()); } else if (input.getSystemId() != null) { parse(new URL(input.getSystemId()).openStream()); } else if (input.getCharacterStream() != null) { throw new SAXNotSupportedException( "A WBXML reader needs binary input" ); } else { throw new SAXException("Empty input source"); } } /** * Creates a WBXML Locator * * @param input * the input source * * @return a WBXML Locator */ private Locator newLocator( final InputSource input ) { return new Locator() { @Override public int getColumnNumber() { return -1; } @Override public int getLineNumber() { return -1; } @Override public String getPublicId() { return input.getPublicId(); } @Override public String getSystemId() { return input.getSystemId(); } }; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#parse(java.lang.String) */ @Override public void parse( String systemId ) throws IOException,SAXException { parse(new InputSource(systemId)); } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setContentHandler(org.xml.sax.ContentHandler) */ @Override public void setContentHandler( ContentHandler handler ) { this.contentHandler = handler; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setDTDHandler(org.xml.sax.DTDHandler) */ @Override public void setDTDHandler( DTDHandler handler ) { // no DTD will be provided } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setEntityResolver(org.xml.sax.EntityResolver) */ @Override public void setEntityResolver( EntityResolver resolver ) { // no entity resolution will take place } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler) */ @Override public void setErrorHandler( ErrorHandler handler ) { this.errorHandler = handler; } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setFeature(java.lang.String, boolean) */ @Override public void setFeature( String name, boolean value ) throws SAXNotRecognizedException,SAXNotSupportedException { if (WBXMLReader.EXHAUST.equals(name)) { this.exhaust = value; } else throw new SAXNotRecognizedException( "Feature: " + name ); } /* * (non-Javadoc) * * @see org.xml.sax.XMLReader#setProperty(java.lang.String, java.lang.Object) */ @Override public void setProperty( String name, Object value ) throws SAXNotRecognizedException,SAXNotSupportedException { if (WBXMLPlugIns.PLUG_IN.equals(name)) { this.plugIn = (PlugIn) value; } else { throw new SAXNotRecognizedException("Property: " + name); } } }