|
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.util.logging.Level; |
|
46 import java.util.logging.Logger; |
|
47 |
|
48 /** |
|
49 * Factory for {@link XMLStreamWriter}. |
|
50 * |
|
51 * <p> |
|
52 * This wraps {@link XMLOutputFactory} and allows us to reuse {@link XMLStreamWriter} instances |
|
53 * when appropriate. |
|
54 * |
|
55 * @author Kohsuke Kawaguchi |
|
56 */ |
|
57 @SuppressWarnings("StaticNonFinalUsedInInitialization") |
|
58 public abstract class XMLStreamWriterFactory { |
|
59 |
|
60 private static final Logger LOGGER = Logger.getLogger(XMLStreamWriterFactory.class.getName()); |
|
61 |
|
62 /** |
|
63 * Singleton instance. |
|
64 */ |
|
65 private static volatile ContextClassloaderLocal<XMLStreamWriterFactory> writerFactory = |
|
66 new ContextClassloaderLocal<XMLStreamWriterFactory>() { |
|
67 |
|
68 @Override |
|
69 protected XMLStreamWriterFactory initialValue() { |
|
70 XMLOutputFactory xof = null; |
|
71 if (Boolean.getBoolean(XMLStreamWriterFactory.class.getName()+".woodstox")) { |
|
72 try { |
|
73 xof = (XMLOutputFactory)Class.forName("com.ctc.wstx.stax.WstxOutputFactory").newInstance(); |
|
74 } catch (Exception e) { |
|
75 // Ignore and fallback to default XMLOutputFactory |
|
76 } |
|
77 } |
|
78 if (xof == null) { |
|
79 xof = XMLOutputFactory.newInstance(); |
|
80 } |
|
81 |
|
82 XMLStreamWriterFactory f=null; |
|
83 |
|
84 // this system property can be used to disable the pooling altogether, |
|
85 // in case someone hits an issue with pooling in the production system. |
|
86 if (!Boolean.getBoolean(XMLStreamWriterFactory.class.getName()+".noPool")) { |
|
87 try { |
|
88 Class<?> clazz = xof.createXMLStreamWriter(new StringWriter()).getClass(); |
|
89 if (clazz.getName().startsWith("com.sun.xml.internal.stream.")) { |
|
90 f = new Zephyr(xof,clazz); |
|
91 } |
|
92 } catch (XMLStreamException ex) { |
|
93 Logger.getLogger(XMLStreamWriterFactory.class.getName()).log(Level.INFO, null, ex); |
|
94 } catch (NoSuchMethodException 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 } catch (NoSuchMethodException e) { |
|
286 return null; // this xof wasn't Zephyr |
|
287 } |
|
288 } |
|
289 |
|
290 private Zephyr(XMLOutputFactory xof, Class clazz) throws NoSuchMethodException { |
|
291 this.xof = xof; |
|
292 |
|
293 zephyrClass = clazz; |
|
294 setOutputMethod = clazz.getMethod("setOutput", StreamResult.class, String.class); |
|
295 resetMethod = clazz.getMethod("reset"); |
|
296 } |
|
297 |
|
298 /** |
|
299 * Fetchs an instance from the pool if available, otherwise null. |
|
300 */ |
|
301 private @Nullable XMLStreamWriter fetch() { |
|
302 XMLStreamWriter sr = pool.get(); |
|
303 if(sr==null) return null; |
|
304 pool.set(null); |
|
305 return sr; |
|
306 } |
|
307 |
|
308 @Override |
|
309 public XMLStreamWriter doCreate(OutputStream out) { |
|
310 return doCreate(out,"UTF-8"); |
|
311 } |
|
312 |
|
313 @Override |
|
314 public XMLStreamWriter doCreate(OutputStream out, String encoding) { |
|
315 XMLStreamWriter xsw = fetch(); |
|
316 if(xsw!=null) { |
|
317 // try to reuse |
|
318 try { |
|
319 resetMethod.invoke(xsw); |
|
320 setOutputMethod.invoke(xsw,new StreamResult(out),encoding); |
|
321 } catch (IllegalAccessException e) { |
|
322 throw new XMLReaderException("stax.cantCreate",e); |
|
323 } catch (InvocationTargetException e) { |
|
324 throw new XMLReaderException("stax.cantCreate",e); |
|
325 } |
|
326 } else { |
|
327 // create a new instance |
|
328 try { |
|
329 xsw = xof.createXMLStreamWriter(out,encoding); |
|
330 } catch (XMLStreamException e) { |
|
331 throw new XMLReaderException("stax.cantCreate",e); |
|
332 } |
|
333 } |
|
334 return new HasEncodingWriter(xsw, encoding); |
|
335 } |
|
336 |
|
337 @Override |
|
338 public void doRecycle(XMLStreamWriter r) { |
|
339 if (r instanceof HasEncodingWriter) { |
|
340 r = ((HasEncodingWriter)r).getWriter(); |
|
341 } |
|
342 if(zephyrClass.isInstance(r)) { |
|
343 // this flushes the underlying stream, so it might cause chunking issue |
|
344 try { |
|
345 r.close(); |
|
346 } catch (XMLStreamException e) { |
|
347 throw new WebServiceException(e); |
|
348 } |
|
349 pool.set(r); |
|
350 } |
|
351 if(r instanceof RecycleAware) |
|
352 ((RecycleAware)r).onRecycled(); |
|
353 } |
|
354 } |
|
355 |
|
356 /** |
|
357 * |
|
358 * For {@link javax.xml.stream.XMLOutputFactory} is thread safe. |
|
359 */ |
|
360 public static final class NoLock extends XMLStreamWriterFactory { |
|
361 private final XMLOutputFactory xof; |
|
362 |
|
363 public NoLock(XMLOutputFactory xof) { |
|
364 this.xof = xof; |
|
365 } |
|
366 |
|
367 @Override |
|
368 public XMLStreamWriter doCreate(OutputStream out) { |
|
369 return doCreate(out, SOAPBindingCodec.UTF8_ENCODING); |
|
370 } |
|
371 |
|
372 @Override |
|
373 public XMLStreamWriter doCreate(OutputStream out, String encoding) { |
|
374 try { |
|
375 XMLStreamWriter writer = xof.createXMLStreamWriter(out,encoding); |
|
376 return new HasEncodingWriter(writer, encoding); |
|
377 } catch (XMLStreamException e) { |
|
378 throw new XMLReaderException("stax.cantCreate",e); |
|
379 } |
|
380 } |
|
381 |
|
382 @Override |
|
383 public void doRecycle(XMLStreamWriter r) { |
|
384 // no recycling |
|
385 } |
|
386 |
|
387 } |
|
388 |
|
389 private static class HasEncodingWriter extends XMLStreamWriterFilter implements HasEncoding { |
|
390 private final String encoding; |
|
391 |
|
392 HasEncodingWriter(XMLStreamWriter writer, String encoding) { |
|
393 super(writer); |
|
394 this.encoding = encoding; |
|
395 } |
|
396 |
|
397 @Override |
|
398 public String getEncoding() { |
|
399 return encoding; |
|
400 } |
|
401 |
|
402 XMLStreamWriter getWriter() { |
|
403 return writer; |
|
404 } |
|
405 } |
|
406 } |