View Javadoc
1   /*
2    * Copyright 2011 Jakub Narloch
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.exolab.castor.xml.util;
17  
18  import org.castor.core.util.Assert;
19  import org.exolab.castor.xml.Namespaces;
20  import org.exolab.castor.xml.NamespacesStack;
21  import org.xml.sax.Attributes;
22  import org.xml.sax.SAXException;
23  import org.xml.sax.helpers.DefaultHandler;
24  
25  import javax.xml.namespace.QName;
26  import javax.xml.stream.XMLEventFactory;
27  import javax.xml.stream.XMLEventWriter;
28  import javax.xml.stream.XMLStreamException;
29  import javax.xml.stream.events.Attribute;
30  import javax.xml.stream.events.Namespace;
31  import java.util.Enumeration;
32  import java.util.Iterator;
33  
34  /**
35   * A document handler that uses internally a instance of {@link javax.xml.stream.XMLEventWriter} to output the result
36   * xml.
37   *
38   * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
39   * @version 1.3.3
40   * @since 1.3.3
41   */
42  public class StaxEventHandler extends DefaultHandler {
43  
44      /**
45       * Instance of {@link XMLEventFactory} that will be used to create instances of xml events.
46       */
47      private final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
48  
49      /**
50       * Instance of {@link XMLEventWriter} to be used to write the marshalled object.
51       */
52      private final XMLEventWriter xmlEventWriter;
53  
54      /**
55       * Instance of {@link Namespaces} used for handling the namespace.
56       */
57      private NamespacesStack namespacesStack = new NamespacesStack();
58  
59      /**
60       * Flag indicating whether the new namespace scope is required to create.
61       */
62      private boolean createNamespaceScope = true;
63  
64      /**
65       * Creates new instance of {@link StaxEventHandler} with given {@link XMLEventWriter}.
66       *
67       * @param xmlEventWriter the {@link XMLEventWriter} to be used
68       *
69       * @throws IllegalArgumentException if xmlEventWriter is null
70       */
71      public StaxEventHandler(XMLEventWriter xmlEventWriter) {
72          Assert.paramNotNull(xmlEventWriter, "xmlEventWriter");
73  
74          this.xmlEventWriter = xmlEventWriter;
75      }
76  
77      @Override
78      public void startDocument() throws SAXException {
79          try {
80              // writes the start of document
81              xmlEventWriter.add(eventFactory.createStartDocument());
82          } catch (XMLStreamException e) {
83              convertToSAXException("Error occurred when writing document start.", e);
84          }
85      }
86  
87      @Override
88      public void endDocument() throws SAXException {
89          try {
90              // writes the end of document
91              xmlEventWriter.add(eventFactory.createEndDocument());
92          } catch (XMLStreamException e) {
93              convertToSAXException("Error occurred when writing document end.", e);
94          }
95      }
96  
97      @Override
98      public void startPrefixMapping(String prefix, String uri) throws SAXException {
99          if (createNamespaceScope) {
100             namespacesStack.addNewNamespaceScope();
101             createNamespaceScope = false;
102         }
103 
104         namespacesStack.addNamespace(prefix, uri);
105     }
106 
107     @Override
108     public void startElement(String uri, String localName,
109                              String qName, Attributes attributes) throws SAXException {
110         try {
111             // writes the start of element
112             xmlEventWriter.add(eventFactory.createStartElement(new QName(qName),
113                     new AttributeIterator(attributes), new NamespaceIterator(namespacesStack)));
114         } catch (XMLStreamException e) {
115             convertToSAXException("Error occurred when writing element start.", e);
116         }
117     }
118 
119     @Override
120     public void endElement(String uri, String localName, String qName) throws SAXException {
121         try {
122             // writes the end of element
123             xmlEventWriter.add(eventFactory.createEndElement(new QName(qName), null));
124         } catch (XMLStreamException e) {
125             convertToSAXException("Error occurred when writing element end.", e);
126         }
127     }
128 
129     @Override
130     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
131         try {
132             // writes the characters
133             xmlEventWriter.add(eventFactory.createCharacters(new String(ch, start, length)));
134         } catch (XMLStreamException e) {
135             convertToSAXException("Error occurred when writing white spaces.", e);
136         }
137     }
138 
139     @Override
140     public void characters(char[] ch, int start, int length) throws SAXException {
141         try {
142             // writes the characters
143             xmlEventWriter.add(eventFactory.createCharacters(new String(ch, start, length)));
144         } catch (XMLStreamException e) {
145             convertToSAXException("Error occurred when writing characters.", e);
146         }
147     }
148 
149     @Override
150     public void processingInstruction(String target, String data) throws SAXException {
151         try {
152             // writes the processing instruction
153             xmlEventWriter.add(eventFactory.createProcessingInstruction(target, data));
154         } catch (XMLStreamException e) {
155             convertToSAXException("Error occurred when writing processing instruction.", e);
156         }
157     }
158 
159     /**
160      * Converts the passed exception into a {@link SAXException} instance with using the provided error message and
161      * exception cause.
162      *
163      * @param msg the error message
164      * @param e   the inner cause of newly created exception
165      *
166      * @throws SAXException the newly created exception instance
167      */
168     private void convertToSAXException(String msg, XMLStreamException e) throws SAXException {
169         throw new SAXException(msg, e);
170     }
171 
172     /**
173      * An attribute iterator that converts the representation of attributes between sax and stax.
174      *
175      * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
176      * @version 1.3.3
177      * @since 1.3.3
178      */
179     private class AttributeIterator implements Iterator {
180 
181         /**
182          * Represents the list of attributes.
183          */
184         private final Attributes attributes;
185 
186         /**
187          * Represents the index that points to current attributes on the list.
188          */
189         private int index;
190 
191         /**
192          * Creates new instance of {@link AttributeIterator} class.
193          *
194          * @param attributes the list of attributes to use
195          */
196         private AttributeIterator(Attributes attributes) {
197             this.attributes = attributes;
198         }
199 
200         public boolean hasNext() {
201             return index < attributes.getLength();
202         }
203 
204         public Object next() {
205             // creates stax attribute instance
206             Attribute attribute =
207                     eventFactory.createAttribute(attributes.getQName(index), attributes.getValue(index));
208             // increments the current index
209             index++;
210             // returns the instance of created attribute
211             return attribute;
212         }
213 
214         public void remove() {
215             throw new UnsupportedOperationException("Method 'remove' is not supported.");
216         }
217     }
218 
219     /**
220      * An namespace iterator that converts the representation of namespace between internal Castor representation
221      * and stax.
222      *
223      * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
224      * @version 1.3.3
225      * @since 1.3.3
226      */
227     private class NamespaceIterator implements Iterator<Namespace> {
228 
229         /**
230          * Represents the current namespace context.
231          */
232         private final NamespacesStack namespaces;
233 
234         /**
235          * Represents the current namespace context.
236          */
237         private final Enumeration namespaceEnumerator;
238 
239         /**
240          * Indicates whether the default namespace exists.
241          */
242         private boolean hasDefaultNamespace;
243 
244         /**
245          * Indicates whether the default namespace has been written.
246          */
247         private boolean defaultNamespaceWritten;
248 
249         /**
250          * Creates new instance of {@link AttributeIterator} class.
251          *
252          * @param namespaces the list of attributes to use
253          */
254         private NamespaceIterator(NamespacesStack namespaces) {
255             this.namespaces = namespaces;
256             this.namespaceEnumerator = namespaces.getLocalNamespacePrefixes();
257 
258             // retrieves the default namespace
259             String defaultNamespace = namespaces.getDefaultNamespaceURI();
260             if (defaultNamespace != null && defaultNamespace.length() > 0) {
261                 hasDefaultNamespace = true;
262             }
263         }
264 
265         public boolean hasNext() {
266             return hasDefaultNamespace && !defaultNamespaceWritten || namespaceEnumerator.hasMoreElements();
267         }
268 
269         public Namespace next() {
270             Namespace namespace;
271 
272             // creates namespace instance
273             if(hasDefaultNamespace && !defaultNamespaceWritten) {
274 
275                 // creates a default namespace instance
276                 namespace = eventFactory.createNamespace(namespaces.getDefaultNamespaceURI());
277                 defaultNamespaceWritten = true;
278             } else {
279 
280                 // creates a namespace instance
281                 String prefix = (String) namespaceEnumerator.nextElement();
282                 namespace = eventFactory.createNamespace(prefix, namespaces.getNamespaceURI(prefix));
283             }
284 
285             // returns the instance of created namespace
286             return namespace;
287         }
288 
289         public void remove() {
290             throw new UnsupportedOperationException("Method 'remove' is not supported.");
291         }
292     }
293 }