This project has retired. For details please refer to its Attic page.
AtomEntryParser xref
View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.server.impl.atompub;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.math.BigInteger;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  
29  import javax.xml.namespace.QName;
30  import javax.xml.stream.XMLStreamConstants;
31  import javax.xml.stream.XMLStreamException;
32  import javax.xml.stream.XMLStreamReader;
33  import javax.xml.stream.XMLStreamWriter;
34  
35  import org.apache.chemistry.opencmis.commons.PropertyIds;
36  import org.apache.chemistry.opencmis.commons.data.Acl;
37  import org.apache.chemistry.opencmis.commons.data.ContentStream;
38  import org.apache.chemistry.opencmis.commons.data.ObjectData;
39  import org.apache.chemistry.opencmis.commons.data.Properties;
40  import org.apache.chemistry.opencmis.commons.data.PropertyData;
41  import org.apache.chemistry.opencmis.commons.data.PropertyId;
42  import org.apache.chemistry.opencmis.commons.data.PropertyString;
43  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
44  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
45  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
46  import org.apache.chemistry.opencmis.commons.impl.Base64;
47  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
48  import org.apache.chemistry.opencmis.commons.impl.XMLConstants;
49  import org.apache.chemistry.opencmis.commons.impl.XMLConstraints;
50  import org.apache.chemistry.opencmis.commons.impl.XMLConverter;
51  import org.apache.chemistry.opencmis.commons.impl.XMLUtils;
52  import org.apache.chemistry.opencmis.commons.impl.dataobjects.BulkUpdateImpl;
53  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
54  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
55  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
56  import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream;
57  import org.apache.chemistry.opencmis.server.shared.CappedInputStream;
58  import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  /**
63   * Parser for Atom Entries.
64   */
65  public final class AtomEntryParser {
66  
67      private static final Logger LOG = LoggerFactory.getLogger(AtomEntryParser.class);
68  
69      private static final long MAX_STREAM_LENGTH = 10 * 1024 * 1024;
70  
71      private static final String TAG_ENTRY = "entry";
72      private static final String TAG_TITLE = "title";
73      private static final String TAG_OBJECT = "object";
74      private static final String TAG_CONTENT = "content";
75      private static final String TAG_BASE64 = "base64";
76      private static final String TAG_MEDIATYPE = "mediatype";
77      private static final String TAG_FILENAME = "filename";
78      private static final String TAG_TYPE = "type";
79      private static final String TAG_BULK_UPDATE = "bulkUpdate";
80  
81      private static final String ATTR_SRC = "src";
82      private static final String ATTR_TYPE = "type";
83  
84      private boolean ignoreAtomContentSrc;
85  
86      private CappedInputStream cappedStream;
87  
88      private final TempStoreOutputStreamFactory streamFactory;
89  
90      private ObjectData object;
91      private ContentStreamImpl atomContentStream;
92      private ContentStreamImpl cmisContentStream;
93      private TypeDefinition typeDef;
94      private BulkUpdateImpl bulkUpdate;
95  
96      /**
97       * Constructor.
98       */
99      public AtomEntryParser(TempStoreOutputStreamFactory streamFactory) {
100         this.streamFactory = streamFactory;
101     }
102 
103     /**
104      * Constructor that immediately parses the given stream.
105      */
106     public AtomEntryParser(InputStream stream, TempStoreOutputStreamFactory streamFactory) throws XMLStreamException,
107             IOException {
108         this(streamFactory);
109         parse(stream);
110     }
111 
112     /**
113      * Sets the flag controlling whether atom content src (external content) is
114      * ignored. This flag is false by default (not ignored).
115      */
116     public void setIgnoreAtomContentSrc(boolean ignoreAtomContentSrc) {
117         this.ignoreAtomContentSrc = ignoreAtomContentSrc;
118     }
119 
120     /**
121      * Returns the object.
122      */
123     public ObjectData getObject() {
124         return object;
125     }
126 
127     /**
128      * Returns the properties of the object.
129      */
130     public Properties getProperties() {
131         return (object == null ? null : object.getProperties());
132     }
133 
134     /**
135      * Returns the Id of the object.
136      */
137     public String getId() {
138         Properties properties = getProperties();
139         if (properties == null) {
140             return null;
141         }
142 
143         Map<String, PropertyData<?>> propertiesMap = properties.getProperties();
144         if (propertiesMap == null) {
145             return null;
146         }
147 
148         PropertyData<?> property = propertiesMap.get(PropertyIds.OBJECT_ID);
149         if (property instanceof PropertyId) {
150             return ((PropertyId) property).getFirstValue();
151         }
152 
153         return null;
154     }
155 
156     /**
157      * Returns the ACL of the object.
158      */
159     public Acl getAcl() {
160         return (object == null ? null : object.getAcl());
161     }
162 
163     /**
164      * Returns the policy id list of the object.
165      */
166     public List<String> getPolicyIds() {
167         if ((object == null) || (object.getPolicyIds() == null)) {
168             return null;
169         }
170 
171         return object.getPolicyIds().getPolicyIds();
172     }
173 
174     /**
175      * Returns the content stream.
176      */
177     public ContentStream getContentStream() {
178         return (cmisContentStream == null ? atomContentStream : cmisContentStream);
179     }
180 
181     /**
182      * Returns the type definition.
183      */
184     public TypeDefinition getTypeDefinition() {
185         return typeDef;
186     }
187 
188     /**
189      * Returns the bulk update data.
190      */
191     public BulkUpdateImpl getBulkUpdate() {
192         return bulkUpdate;
193     }
194 
195     /**
196      * Parses the stream.
197      */
198     public void parse(InputStream stream) throws XMLStreamException, IOException {
199         release();
200 
201         if (stream == null) {
202             return;
203         }
204 
205         cappedStream = new CappedInputStream(stream, MAX_STREAM_LENGTH);
206         XMLStreamReader parser = XMLUtils.createParser(cappedStream);
207 
208         try {
209             while (true) {
210                 int event = parser.getEventType();
211                 if (event == XMLStreamConstants.START_ELEMENT) {
212                     QName name = parser.getName();
213 
214                     if (XMLConstants.NAMESPACE_ATOM.equals(name.getNamespaceURI())
215                             && (TAG_ENTRY.equals(name.getLocalPart()))) {
216                         parseEntry(parser);
217                         break;
218                     } else {
219                         throw new CmisInvalidArgumentException("XML is not an Atom entry!");
220                     }
221                 }
222 
223                 if (!XMLUtils.next(parser)) {
224                     break;
225                 }
226             }
227         } catch (XMLStreamException xse) {
228             release();
229             throw xse;
230         } catch (IOException ioe) {
231             release();
232             throw ioe;
233         } catch (RuntimeException re) {
234             release();
235             throw re;
236         } finally {
237             try {
238                 parser.close();
239             } catch (XMLStreamException xse) {
240                 if (LOG.isWarnEnabled()) {
241                     LOG.warn("Parser couldn't be closed: {}", xse.toString(), xse);
242                 }
243             }
244         }
245     }
246 
247     /**
248      * Releases all resources.
249      */
250     public void release() {
251         object = null;
252         typeDef = null;
253         bulkUpdate = null;
254         closeAtomContentStream();
255         closeCmisContentStream();
256     }
257 
258     /**
259      * Closes the Atom content stream.
260      */
261     private void closeAtomContentStream() {
262         IOUtils.closeQuietly(atomContentStream);
263         atomContentStream = null;
264     }
265 
266     /**
267      * Closes the CMIS content stream.
268      */
269     private void closeCmisContentStream() {
270         IOUtils.closeQuietly(cmisContentStream);
271         cmisContentStream = null;
272     }
273 
274     /**
275      * Parses an Atom entry.
276      */
277     private void parseEntry(XMLStreamReader parser) throws XMLStreamException, IOException {
278         String atomTitle = null;
279 
280         XMLUtils.next(parser);
281 
282         // walk through all tags in entry
283         while (true) {
284             int event = parser.getEventType();
285             if (event == XMLStreamConstants.START_ELEMENT) {
286                 QName name = parser.getName();
287 
288                 if (XMLConstants.NAMESPACE_RESTATOM.equals(name.getNamespaceURI())) {
289                     if (TAG_OBJECT.equals(name.getLocalPart())) {
290                         parseObject(parser);
291                     } else if (TAG_TYPE.equals(name.getLocalPart())) {
292                         parseTypeDefinition(parser);
293                     } else if (TAG_BULK_UPDATE.equals(name.getLocalPart())) {
294                         parseBulkUpdate(parser);
295                     } else if (TAG_CONTENT.equals(name.getLocalPart())) {
296                         parseCmisContent(parser);
297                     } else {
298                         XMLUtils.skip(parser);
299                     }
300                 } else if (XMLConstants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
301                     if (TAG_CONTENT.equals(name.getLocalPart())) {
302                         parseAtomContent(parser);
303                     } else if (TAG_TITLE.equals(name.getLocalPart())) {
304                         atomTitle = XMLUtils.readText(parser, XMLConstraints.MAX_STRING_LENGTH);
305                     } else {
306                         XMLUtils.skip(parser);
307                     }
308                 } else {
309                     XMLUtils.skip(parser);
310                 }
311             } else if (event == XMLStreamConstants.END_ELEMENT) {
312                 break;
313             } else {
314                 if (!XMLUtils.next(parser)) {
315                     break;
316                 }
317             }
318         }
319 
320         // overwrite cmis:name with Atom title
321         if ((object != null) && (object.getProperties() != null) && (atomTitle != null) && (atomTitle.length() > 0)) {
322             PropertyString nameProperty = new PropertyStringImpl(PropertyIds.NAME, atomTitle);
323             ((PropertiesImpl) object.getProperties()).replaceProperty(nameProperty);
324         }
325     }
326 
327     /**
328      * Parses a CMIS object.
329      */
330     private void parseObject(XMLStreamReader parser) throws XMLStreamException {
331         object = XMLConverter.convertObject(parser);
332     }
333 
334     /**
335      * Parses a CMIS type.
336      */
337     private void parseTypeDefinition(XMLStreamReader parser) throws XMLStreamException {
338         typeDef = XMLConverter.convertTypeDefinition(parser);
339     }
340 
341     /**
342      * Parses a bluk update.
343      */
344     private void parseBulkUpdate(XMLStreamReader parser) throws XMLStreamException {
345         bulkUpdate = XMLConverter.convertBulkUpdate(parser);
346     }
347 
348     /**
349      * Extract the content stream.
350      * 
351      * @throws XMLStreamException
352      * @throws IOException
353      */
354     private void parseAtomContent(XMLStreamReader parser) throws XMLStreamException, IOException {
355         if (atomContentStream != null) {
356             closeAtomContentStream();
357             throw new CmisInvalidArgumentException("More than one content provided!");
358         }
359 
360         if (cmisContentStream != null) {
361             // CMIS content takes precedence (see CMIS spec)
362             XMLUtils.skip(parser);
363             return;
364         }
365 
366         atomContentStream = new ContentStreamImpl();
367 
368         // read attributes
369         String type = "text";
370         String mimeType = "text/plain";
371         for (int i = 0; i < parser.getAttributeCount(); i++) {
372             QName attrName = parser.getAttributeName(i);
373             if (ATTR_TYPE.equals(attrName.getLocalPart())) {
374                 if (parser.getAttributeValue(i) != null) {
375                     type = parser.getAttributeValue(i).trim().toLowerCase(Locale.ENGLISH);
376                 }
377             } else if (ATTR_SRC.equals(attrName.getLocalPart())) {
378                 if (ignoreAtomContentSrc) {
379                     atomContentStream = null;
380                     XMLUtils.skip(parser);
381                     return;
382                 }
383                 throw new CmisNotSupportedException("External content not supported!");
384             }
385         }
386 
387         TempStoreOutputStream tsos = null;
388         if (type.equals("text")) {
389             mimeType = "text/plain";
390             tsos = readContentBytes(parser, mimeType);
391         } else if (type.equals("html")) {
392             mimeType = "text/html";
393             tsos = readContentBytes(parser, mimeType);
394         } else if (type.equals("xhtml")) {
395             mimeType = "application/xhtml+xml";
396             tsos = copy(parser, mimeType);
397         } else if (type.endsWith("/xml") || type.endsWith("+xml")) {
398             mimeType = type;
399             tsos = copy(parser, mimeType);
400         } else if (type.startsWith("text/")) {
401             mimeType = type;
402             tsos = readContentBytes(parser, mimeType);
403         } else {
404             mimeType = type;
405             tsos = readBase64(parser, mimeType, null);
406         }
407 
408         atomContentStream.setMimeType(mimeType);
409 
410         if (tsos != null) {
411             try {
412                 atomContentStream.setStream(tsos.getInputStream());
413                 atomContentStream.setLength(BigInteger.valueOf(tsos.getLength()));
414             } catch (IOException e) {
415                 tsos.destroy(e);
416                 throw e;
417             }
418         }
419     }
420 
421     /**
422      * Extract the content stream.
423      */
424     private void parseCmisContent(XMLStreamReader parser) throws XMLStreamException, IOException {
425         closeAtomContentStream();
426         if (cmisContentStream != null) {
427             closeCmisContentStream();
428             throw new CmisInvalidArgumentException("More than one content provided!");
429         }
430 
431         cmisContentStream = new ContentStreamImpl();
432 
433         XMLUtils.next(parser);
434 
435         // walk through all tags in content
436         while (true) {
437             int event = parser.getEventType();
438             if (event == XMLStreamConstants.START_ELEMENT) {
439                 QName name = parser.getName();
440 
441                 if (XMLConstants.NAMESPACE_RESTATOM.equals(name.getNamespaceURI())) {
442                     if (TAG_MEDIATYPE.equals(name.getLocalPart())) {
443                         cmisContentStream.setMimeType(XMLUtils.readText(parser, XMLConstraints.MAX_STRING_LENGTH));
444                     } else if (TAG_BASE64.equals(name.getLocalPart())) {
445                         TempStoreOutputStream tsos = readBase64(parser, cmisContentStream.getMimeType(),
446                                 cmisContentStream.getFileName());
447                         try {
448                             cmisContentStream.setStream(tsos.getInputStream());
449                             cmisContentStream.setLength(BigInteger.valueOf(tsos.getLength()));
450                         } catch (IOException e) {
451                             tsos.destroy(e);
452                             throw e;
453                         }
454                     } else {
455                         XMLUtils.skip(parser);
456                     }
457                 } else if (XMLConstants.NAMESPACE_APACHE_CHEMISTRY.equals(name.getNamespaceURI())) {
458                     if (TAG_FILENAME.equals(name.getLocalPart())) {
459                         cmisContentStream.setFileName(XMLUtils.readText(parser, XMLConstraints.MAX_STRING_LENGTH));
460                     } else {
461                         XMLUtils.skip(parser);
462                     }
463                 } else {
464                     XMLUtils.skip(parser);
465                 }
466             } else if (event == XMLStreamConstants.END_ELEMENT) {
467                 break;
468             } else {
469                 if (!XMLUtils.next(parser)) {
470                     break;
471                 }
472             }
473         }
474 
475         XMLUtils.next(parser);
476     }
477 
478     /**
479      * Parses a tag that contains content bytes.
480      */
481     private TempStoreOutputStream readContentBytes(XMLStreamReader parser, String mimeType) throws XMLStreamException,
482             IOException {
483         TempStoreOutputStream bufferStream = streamFactory.newOutputStream();
484         bufferStream.setMimeType(mimeType);
485 
486         XMLUtils.next(parser);
487 
488         try {
489             while (true) {
490                 int event = parser.getEventType();
491                 if (event == XMLStreamConstants.END_ELEMENT) {
492                     break;
493                 } else if (event == XMLStreamConstants.CHARACTERS) {
494                     String s = parser.getText();
495                     if (s != null) {
496                         byte[] bytes = IOUtils.toUTF8Bytes(s);
497                         bufferStream.write(bytes);
498                         cappedStream.deductBytes(bytes.length);
499                     }
500                 } else if (event == XMLStreamConstants.START_ELEMENT) {
501                     bufferStream.destroy(null);
502                     throw new CmisInvalidArgumentException("Unexpected tag: " + parser.getName());
503                 }
504 
505                 if (!XMLUtils.next(parser)) {
506                     break;
507                 }
508             }
509         } catch (XMLStreamException xse) {
510             // remove temp file
511             bufferStream.destroy(xse);
512             throw xse;
513         } catch (IOException ioe) {
514             // remove temp file
515             bufferStream.destroy(ioe);
516             throw ioe;
517         }
518 
519         XMLUtils.next(parser);
520 
521         return bufferStream;
522     }
523 
524     /**
525      * Parses a tag that contains base64 encoded content.
526      */
527     private TempStoreOutputStream readBase64(XMLStreamReader parser, String mimeType, String filename)
528             throws XMLStreamException, IOException {
529         TempStoreOutputStream bufferStream = streamFactory.newOutputStream();
530         bufferStream.setMimeType(mimeType);
531         bufferStream.setFileName(filename);
532         Base64.OutputStream b64stream = new Base64.OutputStream(bufferStream, Base64.DECODE);
533 
534         XMLUtils.next(parser);
535 
536         try {
537             while (true) {
538                 int event = parser.getEventType();
539                 if (event == XMLStreamConstants.END_ELEMENT) {
540                     break;
541                 } else if (event == XMLStreamConstants.CHARACTERS) {
542                     int len = parser.getTextLength();
543                     if (len > 0) {
544                         char[] chars = parser.getTextCharacters();
545                         int offset = parser.getTextStart();
546                         for (int i = 0; i < len; i++) {
547                             // it's base64/ASCII
548                             b64stream.write(chars[offset + i]);
549                         }
550                         cappedStream.deductBytes(len);
551                     }
552                 } else if (event == XMLStreamConstants.START_ELEMENT) {
553                     b64stream.close();
554                     bufferStream.destroy(null);
555                     throw new CmisInvalidArgumentException("Unexpected tag: " + parser.getName());
556                 }
557 
558                 if (!XMLUtils.next(parser)) {
559                     break;
560                 }
561             }
562 
563             b64stream.close();
564         } catch (XMLStreamException xse) {
565             // remove temp file
566             bufferStream.destroy(xse);
567             throw xse;
568         } catch (IOException ioe) {
569             // remove temp file
570             bufferStream.destroy(ioe);
571             throw ioe;
572         }
573 
574         XMLUtils.next(parser);
575 
576         return bufferStream;
577     }
578 
579     /**
580      * Copies a subtree into a stream.
581      */
582     private TempStoreOutputStream copy(XMLStreamReader parser, String mimeType) throws XMLStreamException, IOException {
583         // create a writer
584         TempStoreOutputStream bufferStream = streamFactory.newOutputStream();
585         bufferStream.setMimeType(mimeType);
586 
587         try {
588             XMLStreamWriter writer = XMLUtils.createWriter(bufferStream);
589 
590             writer.writeStartDocument();
591 
592             // copy subtree
593             int level = 1;
594             while (XMLUtils.next(parser)) {
595                 int event = parser.getEventType();
596                 if (event == XMLStreamConstants.START_ELEMENT) {
597                     copyStartElement(parser, writer);
598                     level++;
599                 } else if (event == XMLStreamConstants.CHARACTERS) {
600                     writer.writeCharacters(parser.getText());
601                 } else if (event == XMLStreamConstants.COMMENT) {
602                     writer.writeComment(parser.getText());
603                 } else if (event == XMLStreamConstants.CDATA) {
604                     writer.writeCData(parser.getText());
605                 } else if (event == XMLStreamConstants.END_ELEMENT) {
606                     level--;
607                     if (level == 0) {
608                         break;
609                     }
610                     writer.writeEndElement();
611                 } else {
612                     break;
613                 }
614             }
615 
616             writer.writeEndDocument();
617             writer.flush();
618 
619             bufferStream.close();
620         } catch (XMLStreamException xse) {
621             // remove temp file
622             bufferStream.destroy(xse);
623             throw xse;
624         } catch (IOException ioe) {
625             // remove temp file
626             bufferStream.destroy(ioe);
627             throw ioe;
628         }
629 
630         XMLUtils.next(parser);
631 
632         return bufferStream;
633     }
634 
635     /**
636      * Copies a XML start element.
637      */
638     private static void copyStartElement(XMLStreamReader parser, XMLStreamWriter writer) throws XMLStreamException {
639         String namespaceUri = parser.getNamespaceURI();
640         String prefix = parser.getPrefix();
641         String localName = parser.getLocalName();
642 
643         // write start element
644         if (namespaceUri != null) {
645             if ((prefix == null) || (prefix.length() == 0)) {
646                 writer.writeStartElement(localName);
647             } else {
648                 writer.writeStartElement(prefix, localName, namespaceUri);
649             }
650         } else {
651             writer.writeStartElement(localName);
652         }
653 
654         // set namespaces
655         for (int i = 0; i < parser.getNamespaceCount(); i++) {
656             addNamespace(writer, parser.getNamespacePrefix(i), parser.getNamespaceURI(i));
657         }
658         addNamespaceIfMissing(writer, prefix, namespaceUri);
659 
660         // write attributes
661         for (int i = 0; i < parser.getAttributeCount(); i++) {
662             String attrNamespaceUri = parser.getAttributeNamespace(i);
663             String attrPrefix = parser.getAttributePrefix(i);
664             String attrName = parser.getAttributeLocalName(i);
665             String attrValue = parser.getAttributeValue(i);
666 
667             if ((attrNamespaceUri == null) || (attrNamespaceUri.trim().length() == 0)) {
668                 writer.writeAttribute(attrName, attrValue);
669             } else if ((attrPrefix == null) || (attrPrefix.trim().length() == 0)) {
670                 writer.writeAttribute(attrNamespaceUri, attrName, attrValue);
671             } else {
672                 addNamespaceIfMissing(writer, attrPrefix, attrNamespaceUri);
673                 writer.writeAttribute(attrPrefix, attrNamespaceUri, attrName, attrValue);
674             }
675         }
676     }
677 
678     /**
679      * Checks if the given prefix is assigned to the given namespace.
680      */
681     @SuppressWarnings("unchecked")
682     private static void addNamespaceIfMissing(XMLStreamWriter writer, String prefix, String namespaceUri)
683             throws XMLStreamException {
684         if ((namespaceUri == null) || (namespaceUri.trim().length() == 0)) {
685             return;
686         }
687 
688         if (prefix == null) {
689             prefix = "";
690         }
691 
692         Iterator<String> iter = writer.getNamespaceContext().getPrefixes(namespaceUri);
693         if (iter == null) {
694             return;
695         }
696 
697         while (iter.hasNext()) {
698             String p = iter.next();
699             if ((p != null) && (p.equals(prefix))) {
700                 return;
701             }
702         }
703 
704         addNamespace(writer, prefix, namespaceUri);
705     }
706 
707     /**
708      * Adds a namespace to a XML element.
709      */
710     private static void addNamespace(XMLStreamWriter writer, String prefix, String namespaceUri)
711             throws XMLStreamException {
712         if ((prefix == null) || (prefix.trim().length() == 0)) {
713             writer.setDefaultNamespace(namespaceUri);
714             writer.writeDefaultNamespace(namespaceUri);
715         } else {
716             writer.setPrefix(prefix, namespaceUri);
717             writer.writeNamespace(prefix, namespaceUri);
718         }
719     }
720 }