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 * Performs character escaping only for new lines. 222 */ 223 private static class NewLineEscapeHandler implements CharacterEscapeHandler { 224 225 public static final NewLineEscapeHandler theInstance = new NewLineEscapeHandler(); 226 227 @Override 228 public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException { 229 int limit = start+length; 230 int lastEscaped = start; 231 for (int i = start; i < limit; i++) { 232 char c = ch[i]; 233 if (c == '\r' || c == '\n') { 234 if (i != lastEscaped) { 235 out.write(ch, lastEscaped, i - lastEscaped); 236 } 237 lastEscaped = i + 1; 238 if (out instanceof XmlStreamOutWriterAdapter) { 239 try { 240 ((XmlStreamOutWriterAdapter)out).writeEntityRef("#x" + Integer.toHexString(c)); 241 } catch (XMLStreamException e) { 242 throw new IOException("Error writing xml stream", e); 243 } 244 } else { 245 out.write("&#x"); 246 out.write(Integer.toHexString(c)); 247 out.write(';'); 248 } 249 } 250 } 251 252 if (lastEscaped != limit) { 253 out.write(ch, lastEscaped, length - lastEscaped); 254 } 255 } 256 } 257 258 private static final class XmlStreamOutWriterAdapter extends Writer { 259 260 private final XMLStreamWriter writer; 261 262 private XmlStreamOutWriterAdapter(XMLStreamWriter writer) { 263 this.writer = writer; 264 } 265 266 @Override 267 public void write(char[] cbuf, int off, int len) throws IOException { 268 try { 269 writer.writeCharacters(cbuf, off, len); 270 } catch (XMLStreamException e) { 271 throw new IOException("Error writing XML stream", e); 272 } 273 } 274 275 public void writeEntityRef(String entityReference) throws XMLStreamException { 276 writer.writeEntityRef(entityReference); 277 } 278 279 @Override 280 public void flush() throws IOException { 281 try { 282 writer.flush(); 283 } catch (XMLStreamException e) { 284 throw new IOException("Error flushing XML stream", e); 285 } 286 } 287 288 @Override 289 public void close() throws IOException { 290 try { 291 writer.close(); 292 } catch (XMLStreamException e) { 293 throw new IOException("Error closing XML stream", e); 294 } 295 } 296 } 297 }