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