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.ws.api.streaming; 27 28 import com.sun.istack.internal.NotNull; 29 import com.sun.istack.internal.Nullable; 30 import com.sun.xml.internal.ws.encoding.HasEncoding; 31 import com.sun.xml.internal.ws.encoding.SOAPBindingCodec; 32 import com.sun.xml.internal.ws.streaming.XMLReaderException; 33 import com.sun.xml.internal.ws.util.MrJarUtil; 34 import com.sun.xml.internal.ws.util.xml.XMLStreamWriterFilter; 35 36 import javax.xml.stream.XMLOutputFactory; 37 import javax.xml.stream.XMLStreamException; 38 import javax.xml.stream.XMLStreamReader; 39 import javax.xml.stream.XMLStreamWriter; 40 import javax.xml.transform.stream.StreamResult; 41 import javax.xml.ws.WebServiceException; 42 import java.io.OutputStream; 43 import java.io.StringWriter; 44 import java.lang.reflect.InvocationTargetException; 45 import java.lang.reflect.Method; 46 import java.security.AccessController; 47 import java.security.PrivilegedAction; 48 import java.util.logging.Level; 49 import java.util.logging.Logger; 50 51 /** 52 * Factory for {@link XMLStreamWriter}. 53 * 54 * <p> 55 * This wraps {@link XMLOutputFactory} and allows us to reuse {@link XMLStreamWriter} instances 56 * when appropriate. 57 * 58 * @author Kohsuke Kawaguchi 59 */ 60 @SuppressWarnings("StaticNonFinalUsedInInitialization") 61 public abstract class XMLStreamWriterFactory { 62 63 private static final Logger LOGGER = Logger.getLogger(XMLStreamWriterFactory.class.getName()); 64 65 /** 66 * Singleton instance. 67 */ 68 private static volatile ContextClassloaderLocal<XMLStreamWriterFactory> writerFactory = 69 new ContextClassloaderLocal<XMLStreamWriterFactory>() { 70 71 @Override 72 protected XMLStreamWriterFactory initialValue() { 73 XMLOutputFactory xof = null; 74 if (Boolean.getBoolean(XMLStreamWriterFactory.class.getName()+".woodstox")) { 75 try { 76 xof = (XMLOutputFactory)Class.forName("com.ctc.wstx.stax.WstxOutputFactory").newInstance(); 77 } catch (Exception e) { 78 // Ignore and fallback to default XMLOutputFactory 79 } 80 } 81 if (xof == null) { 82 xof = XMLOutputFactory.newInstance(); 83 } 84 85 XMLStreamWriterFactory f=null; 86 87 // this system property can be used to disable the pooling altogether, 88 // in case someone hits an issue with pooling in the production system. 89 if (!MrJarUtil.getNoPoolProperty(XMLStreamWriterFactory.class.getName())) { 90 try { 91 Class<?> clazz = xof.createXMLStreamWriter(new StringWriter()).getClass(); 92 if (clazz.getName().startsWith("com.sun.xml.internal.stream.")) { 93 f = new Zephyr(xof,clazz); 94 } 95 } catch (XMLStreamException ex) { 96 Logger.getLogger(XMLStreamWriterFactory.class.getName()).log(Level.INFO, null, ex); 97 } 98 } 99 100 if(f==null) { 101 // is this Woodstox? 102 if(xof.getClass().getName().equals("com.ctc.wstx.stax.WstxOutputFactory")) 103 f = new NoLock(xof); 104 } 105 if (f == null) 106 f = new Default(xof); 107 108 if (LOGGER.isLoggable(Level.FINE)) { 109 LOGGER.log(Level.FINE, "XMLStreamWriterFactory instance is = {0}", f); 110 } 111 return f; 112 } 113 }; 114 115 /** 116 * See {@link #create(OutputStream)} for the contract. 117 * This method may be invoked concurrently. 118 */ 119 public abstract XMLStreamWriter doCreate(OutputStream out); 120 121 /** 122 * See {@link #create(OutputStream,String)} for the contract. 123 * This method may be invoked concurrently. 124 */ 125 public abstract XMLStreamWriter doCreate(OutputStream out, String encoding); 126 127 /** 128 * See {@link #recycle(XMLStreamWriter)} for the contract. 129 * This method may be invoked concurrently. 130 */ 131 public abstract void doRecycle(XMLStreamWriter r); 132 133 /** 134 * Should be invoked when the code finished using an {@link XMLStreamWriter}. 135 * 136 * <p> 137 * If the recycled instance implements {@link RecycleAware}, 138 * {@link RecycleAware#onRecycled()} will be invoked to let the instance 139 * know that it's being recycled. 140 * 141 * <p> 142 * It is not a hard requirement to call this method on every {@link XMLStreamReader} 143 * instance. Not doing so just reduces the performance by throwing away 144 * possibly reusable instances. So the caller should always consider the effort 145 * it takes to recycle vs the possible performance gain by doing so. 146 * 147 * <p> 148 * This method may be invked by multiple threads concurrently. 149 * 150 * @param r 151 * The {@link XMLStreamReader} instance that the caller finished using. 152 * This could be any {@link XMLStreamReader} implementation, not just 153 * the ones that were created from this factory. So the implementation 154 * of this class needs to be aware of that. 155 */ 156 public static void recycle(XMLStreamWriter r) { 157 get().doRecycle(r); 158 } 159 160 /** 161 * Interface that can be implemented by {@link XMLStreamWriter} to 162 * be notified when it's recycled. 163 * 164 * <p> 165 * This provides a filtering {@link XMLStreamWriter} an opportunity to 166 * recycle its inner {@link XMLStreamWriter}. 167 */ 168 public interface RecycleAware { 169 void onRecycled(); 170 } 171 172 /** 173 * Gets the singleton instance. 174 */ 175 public static @NotNull XMLStreamWriterFactory get() { 176 return writerFactory.get(); 177 } 178 179 /** 180 * Overrides the singleton {@link XMLStreamWriterFactory} instance that 181 * the JAX-WS RI uses. 182 * 183 * @param f 184 * must not be null. 185 */ 186 @SuppressWarnings({"null", "ConstantConditions"}) 187 public static void set(@NotNull XMLStreamWriterFactory f) { 188 if(f==null) throw new IllegalArgumentException(); 189 writerFactory.set(f); 190 } 191 192 /** 193 * Short-cut for {@link #create(OutputStream, String)} with UTF-8. 194 */ 195 public static XMLStreamWriter create(OutputStream out) { 196 return get().doCreate(out); 197 } 198 199 public static XMLStreamWriter create(OutputStream out, String encoding) { 200 return get().doCreate(out, encoding); 201 } 202 203 /** 204 * @deprecated 205 * Use {@link #create(OutputStream)} 206 */ 207 public static XMLStreamWriter createXMLStreamWriter(OutputStream out) { 208 return create(out); 209 } 210 211 /** 212 * @deprecated 213 * Use {@link #create(OutputStream, String)} 214 */ 215 public static XMLStreamWriter createXMLStreamWriter(OutputStream out, String encoding) { 216 return create(out, encoding); 217 } 218 219 /** 220 * @deprecated 221 * Use {@link #create(OutputStream, String)}. The boolean flag was unused anyway. 222 */ 223 public static XMLStreamWriter createXMLStreamWriter(OutputStream out, String encoding, boolean declare) { 224 return create(out,encoding); 225 } 226 227 /** 228 * Default {@link XMLStreamWriterFactory} implementation 229 * that can work with any {@link XMLOutputFactory}. 230 * 231 * <p> 232 * {@link XMLOutputFactory} is not required to be thread-safe, so the 233 * create method on this implementation is synchronized. 234 */ 235 public static final class Default extends XMLStreamWriterFactory { 236 private final XMLOutputFactory xof; 237 238 public Default(XMLOutputFactory xof) { 239 this.xof = xof; 240 } 241 242 @Override 243 public XMLStreamWriter doCreate(OutputStream out) { 244 return doCreate(out,"UTF-8"); 245 } 246 247 @Override 248 public synchronized XMLStreamWriter doCreate(OutputStream out, String encoding) { 249 try { 250 XMLStreamWriter writer = xof.createXMLStreamWriter(out,encoding); 251 return new HasEncodingWriter(writer, encoding); 252 } catch (XMLStreamException e) { 253 throw new XMLReaderException("stax.cantCreate",e); 254 } 255 } 256 257 @Override 258 public void doRecycle(XMLStreamWriter r) { 259 // no recycling 260 } 261 } 262 263 /** 264 * {@link XMLStreamWriterFactory} implementation for Sun's StaX implementation. 265 * 266 * <p> 267 * This implementation supports instance reuse. 268 */ 269 public static final class Zephyr extends XMLStreamWriterFactory { 270 private final XMLOutputFactory xof; 271 private final ThreadLocal<XMLStreamWriter> pool = new ThreadLocal<XMLStreamWriter>(); 272 private final Method resetMethod; 273 private final Method setOutputMethod; 274 private final Class zephyrClass; 275 276 public static XMLStreamWriterFactory newInstance(XMLOutputFactory xof) { 277 try { 278 Class<?> clazz = xof.createXMLStreamWriter(new StringWriter()).getClass(); 279 280 if(!clazz.getName().startsWith("com.sun.xml.internal.stream.")) 281 return null; // nope 282 283 return new Zephyr(xof,clazz); 284 } catch (XMLStreamException e) { 285 return null; // impossible 286 } 287 } 288 289 private Zephyr(XMLOutputFactory xof, Class clazz) { 290 this.xof = xof; 291 292 zephyrClass = clazz; 293 setOutputMethod = getMethod(clazz, "setOutput", StreamResult.class, String.class); 294 resetMethod = getMethod(clazz, "reset"); 295 } 296 297 private static Method getMethod(final Class<?> c, final String methodname, final Class<?>... params) { 298 return AccessController.doPrivileged( 299 new PrivilegedAction<Method>() { 300 @Override 301 public Method run() { 302 try { 303 return c.getMethod(methodname, params); 304 } catch (NoSuchMethodException e) { 305 // impossible 306 throw new NoSuchMethodError(e.getMessage()); 307 } 308 } 309 } 310 ); 311 } 312 313 /** 314 * Fetchs an instance from the pool if available, otherwise null. 315 */ 316 private @Nullable XMLStreamWriter fetch() { 317 XMLStreamWriter sr = pool.get(); 318 if(sr==null) return null; 319 pool.set(null); 320 return sr; 321 } 322 323 @Override 324 public XMLStreamWriter doCreate(OutputStream out) { 325 return doCreate(out,"UTF-8"); 326 } 327 328 @Override 329 public XMLStreamWriter doCreate(OutputStream out, String encoding) { 330 XMLStreamWriter xsw = fetch(); 331 if(xsw!=null) { 332 // try to reuse 333 try { 334 resetMethod.invoke(xsw); 335 setOutputMethod.invoke(xsw,new StreamResult(out),encoding); 336 } catch (IllegalAccessException e) { 337 throw new XMLReaderException("stax.cantCreate",e); 338 } catch (InvocationTargetException e) { 339 throw new XMLReaderException("stax.cantCreate",e); 340 } 341 } else { 342 // create a new instance 343 try { 344 xsw = xof.createXMLStreamWriter(out,encoding); 345 } catch (XMLStreamException e) { 346 throw new XMLReaderException("stax.cantCreate",e); 347 } 348 } 349 return new HasEncodingWriter(xsw, encoding); 350 } 351 352 @Override 353 public void doRecycle(XMLStreamWriter r) { 354 if (r instanceof HasEncodingWriter) { 355 r = ((HasEncodingWriter)r).getWriter(); 356 } 357 if(zephyrClass.isInstance(r)) { 358 // this flushes the underlying stream, so it might cause chunking issue 359 try { 360 r.close(); 361 } catch (XMLStreamException e) { 362 throw new WebServiceException(e); 363 } 364 pool.set(r); 365 } 366 if(r instanceof RecycleAware) 367 ((RecycleAware)r).onRecycled(); 368 } 369 } 370 371 /** 372 * 373 * For {@link javax.xml.stream.XMLOutputFactory} is thread safe. 374 */ 375 public static final class NoLock extends XMLStreamWriterFactory { 376 private final XMLOutputFactory xof; 377 378 public NoLock(XMLOutputFactory xof) { 379 this.xof = xof; 380 } 381 382 @Override 383 public XMLStreamWriter doCreate(OutputStream out) { 384 return doCreate(out, SOAPBindingCodec.UTF8_ENCODING); 385 } 386 387 @Override 388 public XMLStreamWriter doCreate(OutputStream out, String encoding) { 389 try { 390 XMLStreamWriter writer = xof.createXMLStreamWriter(out,encoding); 391 return new HasEncodingWriter(writer, encoding); 392 } catch (XMLStreamException e) { 393 throw new XMLReaderException("stax.cantCreate",e); 394 } 395 } 396 397 @Override 398 public void doRecycle(XMLStreamWriter r) { 399 // no recycling 400 } 401 402 } 403 404 private static class HasEncodingWriter extends XMLStreamWriterFilter implements HasEncoding { 405 private final String encoding; 406 407 HasEncodingWriter(XMLStreamWriter writer, String encoding) { 408 super(writer); 409 this.encoding = encoding; 410 } 411 412 @Override 413 public String getEncoding() { 414 return encoding; 415 } 416 417 XMLStreamWriter getWriter() { 418 return writer; 419 } 420 } 421 }