1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.bind.v2.runtime.output; 27 28 import java.io.IOException; 29 import java.io.Writer; 30 import java.lang.reflect.Constructor; 31 32 import javax.xml.stream.XMLStreamException; 33 import javax.xml.stream.XMLStreamWriter; 34 35 import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler; 36 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; 37 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; 38 39 import org.xml.sax.SAXException; 40 41 /** 42 * {@link XmlOutput} that writes to StAX {@link XMLStreamWriter}. 43 * <p> 44 * TODO: 45 * Finding the optimized FI implementations is a bit hacky and not very 46 * extensible. Can we use the service provider mechanism in general for 47 * concrete implementations of XmlOutputAbstractImpl. 48 * 49 * @author Kohsuke Kawaguchi 50 */ 51 public class XMLStreamWriterOutput extends XmlOutputAbstractImpl { 52 53 /** 54 * Creates a new {@link XmlOutput} from a {@link XMLStreamWriter}. 55 * This method recognizes an FI StAX writer. 56 */ 57 public static XmlOutput create(XMLStreamWriter out, JAXBContextImpl context, CharacterEscapeHandler escapeHandler) { 58 // try optimized path 59 final Class writerClass = out.getClass(); 60 if (writerClass==FI_STAX_WRITER_CLASS) { 61 try { 62 return FI_OUTPUT_CTOR.newInstance(out, context); 63 } catch (Exception e) { 64 } 65 } 66 if (STAXEX_WRITER_CLASS!=null && STAXEX_WRITER_CLASS.isAssignableFrom(writerClass)) { 67 try { 68 return STAXEX_OUTPUT_CTOR.newInstance(out); 69 } catch (Exception e) { 70 } 71 } 72 73 CharacterEscapeHandler xmlStreamEscapeHandler = escapeHandler != null ? 74 escapeHandler : NewLineEscapeHandler.theInstance; 75 76 // otherwise the normal writer. 77 return new XMLStreamWriterOutput(out, xmlStreamEscapeHandler); 78 } 79 80 81 private final XMLStreamWriter out; 82 83 private final CharacterEscapeHandler escapeHandler; 84 85 private final XmlStreamOutWriterAdapter writerWrapper; 86 87 protected final char[] buf = new char[256]; 88 89 protected XMLStreamWriterOutput(XMLStreamWriter out, CharacterEscapeHandler escapeHandler) { 90 this.out = out; 91 this.escapeHandler = escapeHandler; 92 this.writerWrapper = new XmlStreamOutWriterAdapter(out); 93 } 94 95 // not called if we are generating fragments 96 @Override 97 public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException { 98 super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext); 99 if(!fragment) 100 out.writeStartDocument(); 101 } 102 103 @Override 104 public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException { 105 if(!fragment) { 106 out.writeEndDocument(); 107 out.flush(); 108 } 109 super.endDocument(fragment); 110 } 111 112 public void beginStartTag(int prefix, String localName) throws IOException, XMLStreamException { 113 out.writeStartElement( 114 nsContext.getPrefix(prefix), 115 localName, 116 nsContext.getNamespaceURI(prefix)); 117 118 NamespaceContextImpl.Element nse = nsContext.getCurrent(); 119 if(nse.count()>0) { 120 for( int i=nse.count()-1; i>=0; i-- ) { 121 String uri = nse.getNsUri(i); 122 if(uri.length()==0 && nse.getBase()==1) 123 continue; // no point in definint xmlns='' on the root 124 out.writeNamespace(nse.getPrefix(i),uri); 125 } 126 } 127 } 128 129 public void attribute(int prefix, String localName, String value) throws IOException, XMLStreamException { 130 if(prefix==-1) 131 out.writeAttribute(localName,value); 132 else 133 out.writeAttribute( 134 nsContext.getPrefix(prefix), 135 nsContext.getNamespaceURI(prefix), 136 localName, value); 137 } 138 139 public void endStartTag() throws IOException, SAXException { 140 // noop 141 } 142 143 public void endTag(int prefix, String localName) throws IOException, SAXException, XMLStreamException { 144 out.writeEndElement(); 145 } 146 147 public void text(String value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException { 148 if(needsSeparatingWhitespace) 149 out.writeCharacters(" "); 150 escapeHandler.escape(value.toCharArray(), 0, value.length(), false, writerWrapper); 151 } 152 153 public void text(Pcdata value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException { 154 if(needsSeparatingWhitespace) 155 out.writeCharacters(" "); 156 157 int len = value.length(); 158 if(len <buf.length) { 159 value.writeTo(buf,0); 160 out.writeCharacters(buf,0,len); 161 } else { 162 out.writeCharacters(value.toString()); 163 } 164 } 165 166 /** 167 * Reference to FI's XMLStreamWriter class, if FI can be loaded. 168 */ 169 private static final Class FI_STAX_WRITER_CLASS = initFIStAXWriterClass(); 170 private static final Constructor<? extends XmlOutput> FI_OUTPUT_CTOR = initFastInfosetOutputClass(); 171 172 private static Class initFIStAXWriterClass() { 173 try { 174 Class<?> llfisw = Class.forName("com.sun.xml.internal.org.jvnet.fastinfoset.stax.LowLevelFastInfosetStreamWriter"); 175 Class<?> sds = Class.forName("com.sun.xml.internal.fastinfoset.stax.StAXDocumentSerializer"); 176 // Check if StAXDocumentSerializer implements LowLevelFastInfosetStreamWriter 177 if (llfisw.isAssignableFrom(sds)) 178 return sds; 179 else 180 return null; 181 } catch (Throwable e) { 182 return null; 183 } 184 } 185 186 private static Constructor<? extends XmlOutput> initFastInfosetOutputClass() { 187 try { 188 if (FI_STAX_WRITER_CLASS == null) 189 return null; 190 Class c = Class.forName("com.sun.xml.internal.bind.v2.runtime.output.FastInfosetStreamWriterOutput"); 191 return c.getConstructor(FI_STAX_WRITER_CLASS, JAXBContextImpl.class); 192 } catch (Throwable e) { 193 return null; 194 } 195 } 196 197 // 198 // StAX-ex 199 // 200 private static final Class STAXEX_WRITER_CLASS = initStAXExWriterClass(); 201 private static final Constructor<? extends XmlOutput> STAXEX_OUTPUT_CTOR = initStAXExOutputClass(); 202 203 private static Class initStAXExWriterClass() { 204 try { 205 return Class.forName("com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx"); 206 } catch (Throwable e) { 207 return null; 208 } 209 } 210 211 private static Constructor<? extends XmlOutput> initStAXExOutputClass() { 212 try { 213 Class c = Class.forName("com.sun.xml.internal.bind.v2.runtime.output.StAXExStreamWriterOutput"); 214 return c.getConstructor(STAXEX_WRITER_CLASS); 215 } catch (Throwable e) { 216 return null; 217 } 218 } 219 220 221 /** 222 * Performs character escaping only for new lines. 223 */ 224 private static class NewLineEscapeHandler implements CharacterEscapeHandler { 225 226 public static final NewLineEscapeHandler theInstance = new NewLineEscapeHandler(); 227 228 @Override 229 public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException { 230 int limit = start+length; 231 int lastEscaped = start; 232 233 for (int i = start; i < limit; i++) { 234 char c = ch[i]; 235 if (c == '\r' || c == '\n') { 236 if (i != lastEscaped) { 237 out.write(ch, lastEscaped, i - lastEscaped); 238 } 239 lastEscaped = i + 1; 240 if (out instanceof XmlStreamOutWriterAdapter) { 241 try { 242 ((XmlStreamOutWriterAdapter)out).writeEntityRef("#x" + Integer.toHexString(c)); 243 } catch (XMLStreamException e) { 244 throw new IOException("Error writing xml stream", e); 245 } 246 } else { 247 out.write("&#x"); 248 out.write(Integer.toHexString(c)); 249 out.write(';'); 250 } 251 } 252 } 253 if (lastEscaped != limit) { 254 out.write(ch, lastEscaped, length - lastEscaped); 255 } 256 } 257 } 258 259 private static final class XmlStreamOutWriterAdapter extends Writer { 260 261 private final XMLStreamWriter writer; 262 263 private XmlStreamOutWriterAdapter(XMLStreamWriter writer) { 264 this.writer = writer; 265 } 266 267 @Override 268 public void write(char[] cbuf, int off, int len) throws IOException { 269 try { 270 writer.writeCharacters(cbuf, off, len); 271 } catch (XMLStreamException e) { 272 throw new IOException("Error writing XML stream", e); 273 } 274 } 275 276 public void writeEntityRef(String entityReference) throws XMLStreamException { 277 writer.writeEntityRef(entityReference); 278 } 279 280 @Override 281 public void flush() throws IOException { 282 try { 283 writer.flush(); 284 } catch (XMLStreamException e) { 285 throw new IOException("Error flushing XML stream", e); 286 } 287 } 288 289 @Override 290 public void close() throws IOException { 291 try { 292 writer.close(); 293 } catch (XMLStreamException e) { 294 throw new IOException("Error closing XML stream", e); 295 } 296 } 297 } 298 }