View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, 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.fault;
27  
28  import com.sun.istack.internal.NotNull;
29  import com.sun.istack.internal.Nullable;
30  import com.sun.xml.internal.ws.api.SOAPVersion;
31  import com.sun.xml.internal.ws.api.message.Message;
32  import com.sun.xml.internal.ws.api.model.ExceptionType;
33  import com.sun.xml.internal.ws.encoding.soap.SOAP12Constants;
34  import com.sun.xml.internal.ws.encoding.soap.SOAPConstants;
35  import com.sun.xml.internal.ws.encoding.soap.SerializationException;
36  import com.sun.xml.internal.ws.message.jaxb.JAXBMessage;
37  import com.sun.xml.internal.ws.message.FaultMessage;
38  import com.sun.xml.internal.ws.model.CheckedExceptionImpl;
39  import com.sun.xml.internal.ws.model.JavaMethodImpl;
40  import com.sun.xml.internal.ws.spi.db.XMLBridge;
41  import com.sun.xml.internal.ws.util.DOMUtil;
42  import com.sun.xml.internal.ws.util.StringUtils;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Element;
45  import org.w3c.dom.Node;
46  
47  import javax.xml.bind.JAXBContext;
48  import javax.xml.bind.JAXBException;
49  import javax.xml.bind.annotation.XmlTransient;
50  import javax.xml.namespace.QName;
51  import javax.xml.soap.SOAPFault;
52  import javax.xml.soap.Detail;
53  import javax.xml.soap.DetailEntry;
54  import javax.xml.transform.dom.DOMResult;
55  import javax.xml.ws.ProtocolException;
56  import javax.xml.ws.WebServiceException;
57  import javax.xml.ws.soap.SOAPFaultException;
58  import java.lang.reflect.Constructor;
59  import java.lang.reflect.Field;
60  import java.lang.reflect.Method;
61  import java.lang.reflect.ReflectPermission;
62  import java.security.AccessControlContext;
63  import java.security.AccessController;
64  import java.security.Permissions;
65  import java.security.PrivilegedAction;
66  import java.security.ProtectionDomain;
67  import java.util.Iterator;
68  import java.util.Map;
69  import java.util.logging.Level;
70  import java.util.logging.Logger;
71  
72  /**
73   * Base class that represents SOAP 1.1 or SOAP 1.2 fault. This class can be used by the invocation handlers to create
74   * an Exception from a received messge.
75   *
76   * @author Vivek Pandey
77   */
78  public abstract class SOAPFaultBuilder {
79  
80      /**
81       * Gives the {@link DetailType} for a Soap 1.1 or Soap 1.2 message that can be used to create either a checked exception or
82       * a protocol specific exception
83       */
84      abstract DetailType getDetail();
85  
86      abstract void setDetail(DetailType detailType);
87  
88      public @XmlTransient @Nullable QName getFirstDetailEntryName() {
89          DetailType dt = getDetail();
90          if (dt != null) {
91              Node entry = dt.getDetail(0);
92              if (entry != null) {
93                  return new QName(entry.getNamespaceURI(), entry.getLocalName());
94              }
95          }
96          return null;
97      }
98  
99      /**
100      * gives the fault string that can be used to create an {@link Exception}
101      */
102     abstract String getFaultString();
103 
104     /**
105      * This should be called from the client side to throw an {@link Exception} for a given soap mesage
106      */
107     public Throwable createException(Map<QName, CheckedExceptionImpl> exceptions) throws JAXBException {
108         DetailType dt = getDetail();
109         Node detail = null;
110         if(dt != null)  detail = dt.getDetail(0);
111 
112         //return ProtocolException if the detail is not present or there is no checked exception
113         if(detail == null || exceptions == null){
114             // No soap detail, doesnt look like its a checked exception
115             // throw a protocol exception
116             return attachServerException(getProtocolException());
117         }
118 
119         //check if the detail is a checked exception, if not throw a ProtocolException
120         QName detailName = new QName(detail.getNamespaceURI(), detail.getLocalName());
121         CheckedExceptionImpl ce = exceptions.get(detailName);
122         if (ce == null) {
123             //No Checked exception for the received detail QName, throw a SOAPFault exception
124             return attachServerException(getProtocolException());
125 
126         }
127 
128         if (ce.getExceptionType().equals(ExceptionType.UserDefined)) {
129             return attachServerException(createUserDefinedException(ce));
130 
131         }
132         Class exceptionClass = ce.getExceptionClass();
133         try {
134             Constructor constructor = exceptionClass.getConstructor(String.class, (Class) ce.getDetailType().type);
135             Exception exception = (Exception) constructor.newInstance(getFaultString(), getJAXBObject(detail, ce));
136             return attachServerException(exception);
137         } catch (Exception e) {
138             throw new WebServiceException(e);
139         }
140     }
141 
142     /**
143      * To be called to convert a  {@link ProtocolException} and faultcode for a given {@link SOAPVersion} in to a {@link Message}.
144      *
145      * @param soapVersion {@link SOAPVersion#SOAP_11} or {@link SOAPVersion#SOAP_12}
146      * @param ex a ProtocolException
147      * @param faultcode soap faultcode. Its ignored if the {@link ProtocolException} instance is {@link SOAPFaultException} and it has a
148      * faultcode present in the underlying {@link SOAPFault}.
149      * @return {@link Message} representing SOAP fault
150      */
151     public static @NotNull Message createSOAPFaultMessage(@NotNull SOAPVersion soapVersion, @NotNull ProtocolException ex, @Nullable QName faultcode){
152         Object detail = getFaultDetail(null, ex);
153         if(soapVersion == SOAPVersion.SOAP_12)
154             return createSOAP12Fault(soapVersion, ex, detail, null, faultcode);
155         return createSOAP11Fault(soapVersion, ex, detail, null, faultcode);
156     }
157 
158     /**
159      * To be called by the server runtime in the situations when there is an Exception that needs to be transformed in
160      * to a soapenv:Fault payload.
161      *
162      * @param ceModel     {@link CheckedExceptionImpl} model that provides useful informations such as the detail tagname
163      *                    and the Exception associated with it. Caller of this constructor should get the CheckedException
164      *                    model by calling {@link JavaMethodImpl#getCheckedException(Class)}, where
165      *                    Class is t.getClass().
166      *                    <p>
167      *                    If its null then this is not a checked exception  and in that case the soap fault will be
168      *                    serialized only from the exception as described below.
169      * @param ex          Exception that needs to be translated into soapenv:Fault, always non-null.
170      *                    <ul>
171      *                    <li>If t is instance of {@link SOAPFaultException} then its serilaized as protocol exception.
172      *                    <li>If t.getCause() is instance of {@link SOAPFaultException} and t is a checked exception then
173      *                    the soap fault detail is serilaized from t and the fault actor/string/role is taken from t.getCause().
174      *                    </ul>
175      * @param soapVersion non-null
176      */
177     public static Message createSOAPFaultMessage(SOAPVersion soapVersion, CheckedExceptionImpl ceModel, Throwable ex) {
178         // Sometimes InvocationTargetException.getCause() is null
179         // but InvocationTargetException.getTargetException() contains the real exception
180         // even though they are supposed to be equivalent.
181         // If we only look at .getCause this results in the real exception being lost.
182         // Looks like a JDK bug.
183         final Throwable t =
184             ex instanceof java.lang.reflect.InvocationTargetException
185             ?
186             ((java.lang.reflect.InvocationTargetException)ex).getTargetException()
187             :
188             ex;
189         return createSOAPFaultMessage(soapVersion, ceModel, t, null);
190     }
191 
192     /**
193      * Create the Message with the specified faultCode
194      *
195      * @see #createSOAPFaultMessage(SOAPVersion, CheckedExceptionImpl, Throwable)
196      */
197     public static Message createSOAPFaultMessage(SOAPVersion soapVersion, CheckedExceptionImpl ceModel, Throwable ex, QName faultCode) {
198         Object detail = getFaultDetail(ceModel, ex);
199         if(soapVersion == SOAPVersion.SOAP_12)
200             return createSOAP12Fault(soapVersion, ex, detail, ceModel, faultCode);
201         return createSOAP11Fault(soapVersion, ex, detail, ceModel, faultCode);
202     }
203 
204     /**
205      * Server runtime will call this when there is some internal error not resulting from an exception.
206      *
207      * @param soapVersion {@link SOAPVersion#SOAP_11} or {@link SOAPVersion#SOAP_12}
208      * @param faultString must be non-null
209      * @param faultCode   For SOAP 1.1, it must be one of
210      *                    <ul>
211      *                    <li>{@link SOAPVersion#faultCodeClient}
212      *                    <li>{@link SOAPVersion#faultCodeServer}
213      *                    <li>{@link SOAPConstants#FAULT_CODE_MUST_UNDERSTAND}
214      *                    <li>{@link SOAPConstants#FAULT_CODE_VERSION_MISMATCH}
215      *                    </ul>
216      *
217      *                    For SOAP 1.2
218      *                    <ul>
219      *                    <li>{@link SOAPVersion#faultCodeClient}
220      *                    <li>{@link SOAPVersion#faultCodeServer}
221      *                    <li>{@link SOAP12Constants#FAULT_CODE_MUST_UNDERSTAND}
222      *                    <li>{@link SOAP12Constants#FAULT_CODE_VERSION_MISMATCH}
223      *                    <li>{@link SOAP12Constants#FAULT_CODE_DATA_ENCODING_UNKNOWN}
224      *                    </ul>
225      * @return non-null {@link Message}
226      */
227     public static Message createSOAPFaultMessage(SOAPVersion soapVersion, String faultString, QName faultCode) {
228         if (faultCode == null)
229             faultCode = getDefaultFaultCode(soapVersion);
230         return createSOAPFaultMessage(soapVersion, faultString, faultCode, null);
231     }
232 
233     public static Message createSOAPFaultMessage(SOAPVersion soapVersion, SOAPFault fault) {
234         switch (soapVersion) {
235             case SOAP_11:
236                 return JAXBMessage.create(JAXB_CONTEXT, new SOAP11Fault(fault), soapVersion);
237             case SOAP_12:
238                 return JAXBMessage.create(JAXB_CONTEXT, new SOAP12Fault(fault), soapVersion);
239             default:
240                 throw new AssertionError();
241         }
242     }
243 
244     private static Message createSOAPFaultMessage(SOAPVersion soapVersion, String faultString, QName faultCode, Element detail) {
245         switch (soapVersion) {
246             case SOAP_11:
247                 return JAXBMessage.create(JAXB_CONTEXT, new SOAP11Fault(faultCode, faultString, null, detail), soapVersion);
248             case SOAP_12:
249                 return JAXBMessage.create(JAXB_CONTEXT, new SOAP12Fault(faultCode, faultString, detail), soapVersion);
250             default:
251                 throw new AssertionError();
252         }
253     }
254 
255     /**
256      * Creates a DOM node that represents the complete stack trace of the exception,
257      * and attach that to {@link DetailType}.
258      */
259     final void captureStackTrace(@Nullable Throwable t) {
260         if(t==null)     return;
261         if(!captureStackTrace)  return;     // feature disabled
262 
263         try {
264             Document d = DOMUtil.createDom();
265             ExceptionBean.marshal(t,d);
266 
267             DetailType detail = getDetail();
268             if(detail==null)
269             setDetail(detail=new DetailType());
270 
271             detail.getDetails().add(d.getDocumentElement());
272         } catch (JAXBException e) {
273             // this should never happen
274             logger.log(Level.WARNING, "Unable to capture the stack trace into XML",e);
275         }
276     }
277 
278     /**
279      * Initialize the cause of this exception by attaching the server side exception.
280      */
281     private <T extends Throwable> T attachServerException(T t) {
282         DetailType detail = getDetail();
283         if(detail==null)        return t;   // no details
284 
285         for (Element n : detail.getDetails()) {
286             if(ExceptionBean.isStackTraceXml(n)) {
287                 try {
288                     t.initCause(ExceptionBean.unmarshal(n));
289                 } catch (JAXBException e) {
290                     // perhaps incorrectly formatted XML.
291                     logger.log(Level.WARNING, "Unable to read the capture stack trace in the fault",e);
292                 }
293                 return t;
294             }
295         }
296 
297         return t;
298     }
299 
300     abstract protected Throwable getProtocolException();
301 
302     private Object getJAXBObject(Node jaxbBean, CheckedExceptionImpl ce) throws JAXBException {
303         XMLBridge bridge = ce.getBond();
304         return bridge.unmarshal(jaxbBean,null);
305     }
306 
307     private Exception createUserDefinedException(CheckedExceptionImpl ce) {
308         Class exceptionClass = ce.getExceptionClass();
309         Class detailBean = ce.getDetailBean();
310         try{
311             Node detailNode = getDetail().getDetails().get(0);
312             Object jaxbDetail = getJAXBObject(detailNode, ce);
313             Constructor exConstructor;
314             try{
315                 exConstructor = exceptionClass.getConstructor(String.class, detailBean);
316                 return (Exception) exConstructor.newInstance(getFaultString(), jaxbDetail);
317             }catch(NoSuchMethodException e){
318                 exConstructor = exceptionClass.getConstructor(String.class);
319                 return (Exception) exConstructor.newInstance(getFaultString());
320             }
321         } catch (Exception e) {
322             throw new WebServiceException(e);
323         }
324     }
325 
326     private static String getWriteMethod(Field f) {
327         return "set" + StringUtils.capitalize(f.getName());
328     }
329 
330     private static Object getFaultDetail(CheckedExceptionImpl ce, Throwable exception) {
331         if (ce == null)
332             return null;
333         if (ce.getExceptionType().equals(ExceptionType.UserDefined)) {
334             return createDetailFromUserDefinedException(ce, exception);
335         }
336         try {
337             Method m = exception.getClass().getMethod("getFaultInfo");
338             return m.invoke(exception);
339         } catch (Exception e) {
340             throw new SerializationException(e);
341         }
342     }
343 
344     private static Object createDetailFromUserDefinedException(CheckedExceptionImpl ce, Object exception) {
345         Class detailBean = ce.getDetailBean();
346         Field[] fields = detailBean.getDeclaredFields();
347         try {
348             Object detail = detailBean.newInstance();
349             for (Field f : fields) {
350                 Method em = exception.getClass().getMethod(getReadMethod(f));
351                 try {
352                     Method sm = detailBean.getMethod(getWriteMethod(f), em.getReturnType());
353                     sm.invoke(detail, em.invoke(exception));
354                 } catch(NoSuchMethodException ne) {
355                     // Try to use exception bean's public field to populate the value.
356                     Field sf = detailBean.getField(f.getName());
357                     sf.set(detail, em.invoke(exception));
358                 }
359             }
360             return detail;
361         } catch (Exception e) {
362             throw new SerializationException(e);
363         }
364     }
365 
366     private static String getReadMethod(Field f) {
367         if (f.getType().isAssignableFrom(boolean.class))
368             return "is" + StringUtils.capitalize(f.getName());
369         return "get" + StringUtils.capitalize(f.getName());
370     }
371 
372     private static Message createSOAP11Fault(SOAPVersion soapVersion, Throwable e, Object detail, CheckedExceptionImpl ce, QName faultCode) {
373         SOAPFaultException soapFaultException = null;
374         String faultString = null;
375         String faultActor = null;
376         Throwable cause = e.getCause();
377         if (e instanceof SOAPFaultException) {
378             soapFaultException = (SOAPFaultException) e;
379         } else if (cause != null && cause instanceof SOAPFaultException) {
380             soapFaultException = (SOAPFaultException) e.getCause();
381         }
382         if (soapFaultException != null) {
383             QName soapFaultCode = soapFaultException.getFault().getFaultCodeAsQName();
384             if(soapFaultCode != null)
385                 faultCode = soapFaultCode;
386 
387             faultString = soapFaultException.getFault().getFaultString();
388             faultActor = soapFaultException.getFault().getFaultActor();
389         }
390 
391         if (faultCode == null) {
392             faultCode = getDefaultFaultCode(soapVersion);
393         }
394 
395         if (faultString == null) {
396             faultString = e.getMessage();
397             if (faultString == null) {
398                 faultString = e.toString();
399             }
400         }
401         Element detailNode = null;
402         QName firstEntry = null;
403         if (detail == null && soapFaultException != null) {
404             detailNode = soapFaultException.getFault().getDetail();
405             firstEntry = getFirstDetailEntryName((Detail)detailNode);
406         } else if(ce != null){
407             try {
408                 DOMResult dr = new DOMResult();
409                 ce.getBond().marshal(detail,dr);
410                 detailNode = (Element)dr.getNode().getFirstChild();
411                 firstEntry = getFirstDetailEntryName(detailNode);
412             } catch (JAXBException e1) {
413                 //Should we throw Internal Server Error???
414                 faultString = e.getMessage();
415                 faultCode = getDefaultFaultCode(soapVersion);
416             }
417         }
418         SOAP11Fault soap11Fault = new SOAP11Fault(faultCode, faultString, faultActor, detailNode);
419 
420         //Don't fill the stacktrace for Service specific exceptions.
421         if(ce == null) {
422             soap11Fault.captureStackTrace(e);
423         }
424         Message msg = JAXBMessage.create(JAXB_CONTEXT, soap11Fault, soapVersion);
425         return new FaultMessage(msg, firstEntry);
426     }
427 
428     private static @Nullable QName getFirstDetailEntryName(@Nullable Detail detail) {
429         if (detail != null) {
430             Iterator<DetailEntry> it = detail.getDetailEntries();
431             if (it.hasNext()) {
432                 DetailEntry entry = it.next();
433                 return getFirstDetailEntryName(entry);
434             }
435         }
436         return null;
437     }
438 
439     private static @NotNull QName getFirstDetailEntryName(@NotNull Element entry) {
440         return new QName(entry.getNamespaceURI(), entry.getLocalName());
441     }
442 
443     private static Message createSOAP12Fault(SOAPVersion soapVersion, Throwable e, Object detail, CheckedExceptionImpl ce, QName faultCode) {
444         SOAPFaultException soapFaultException = null;
445         CodeType code = null;
446         String faultString = null;
447         String faultRole = null;
448         String faultNode = null;
449         Throwable cause = e.getCause();
450         if (e instanceof SOAPFaultException) {
451             soapFaultException = (SOAPFaultException) e;
452         } else if (cause != null && cause instanceof SOAPFaultException) {
453             soapFaultException = (SOAPFaultException) e.getCause();
454         }
455         if (soapFaultException != null) {
456             SOAPFault fault = soapFaultException.getFault();
457             QName soapFaultCode = fault.getFaultCodeAsQName();
458             if(soapFaultCode != null){
459                 faultCode = soapFaultCode;
460                 code = new CodeType(faultCode);
461                 Iterator iter = fault.getFaultSubcodes();
462                 boolean first = true;
463                 SubcodeType subcode = null;
464                 while(iter.hasNext()){
465                     QName value = (QName)iter.next();
466                     if(first){
467                         SubcodeType sct = new SubcodeType(value);
468                         code.setSubcode(sct);
469                         subcode = sct;
470                         first = false;
471                         continue;
472                     }
473                     subcode = fillSubcodes(subcode, value);
474                 }
475             }
476             faultString = soapFaultException.getFault().getFaultString();
477             faultRole = soapFaultException.getFault().getFaultActor();
478             faultNode = soapFaultException.getFault().getFaultNode();
479         }
480 
481         if (faultCode == null) {
482             faultCode = getDefaultFaultCode(soapVersion);
483             code = new CodeType(faultCode);
484         }else if(code == null){
485             code = new CodeType(faultCode);
486         }
487 
488         if (faultString == null) {
489             faultString = e.getMessage();
490             if (faultString == null) {
491                 faultString = e.toString();
492             }
493         }
494 
495         ReasonType reason = new ReasonType(faultString);
496         Element detailNode = null;
497         QName firstEntry = null;
498         if (detail == null && soapFaultException != null) {
499             detailNode = soapFaultException.getFault().getDetail();
500             firstEntry = getFirstDetailEntryName((Detail)detailNode);
501         } else if(detail != null){
502             try {
503                 DOMResult dr = new DOMResult();
504                 ce.getBond().marshal(detail, dr);
505                 detailNode = (Element)dr.getNode().getFirstChild();
506                 firstEntry = getFirstDetailEntryName(detailNode);
507             } catch (JAXBException e1) {
508                 //Should we throw Internal Server Error???
509                 faultString = e.getMessage();
510             }
511         }
512 
513         SOAP12Fault soap12Fault = new SOAP12Fault(code, reason, faultNode, faultRole, detailNode);
514 
515         //Don't fill the stacktrace for Service specific exceptions.
516         if(ce == null) {
517             soap12Fault.captureStackTrace(e);
518         }
519         Message msg = JAXBMessage.create(JAXB_CONTEXT, soap12Fault, soapVersion);
520         return new FaultMessage(msg, firstEntry);
521     }
522 
523     private static SubcodeType fillSubcodes(SubcodeType parent, QName value){
524         SubcodeType newCode = new SubcodeType(value);
525         parent.setSubcode(newCode);
526         return newCode;
527     }
528 
529     private static QName getDefaultFaultCode(SOAPVersion soapVersion) {
530         return soapVersion.faultCodeServer;
531     }
532 
533     /**
534      * Parses a fault {@link Message} and returns it as a {@link SOAPFaultBuilder}.
535      *
536      * @return always non-null valid object.
537      * @throws JAXBException if the parsing fails.
538      */
539     public static SOAPFaultBuilder create(Message msg) throws JAXBException {
540         return msg.readPayloadAsJAXB(JAXB_CONTEXT.createUnmarshaller());
541     }
542 
543     /**
544      * This {@link JAXBContext} can handle SOAP 1.1/1.2 faults.
545      */
546     private static final JAXBContext JAXB_CONTEXT;
547 
548     private static final Logger logger = Logger.getLogger(SOAPFaultBuilder.class.getName());
549 
550     /**
551      * Set to false if you don't want the generated faults to have stack trace in it.
552      */
553     public static final boolean captureStackTrace;
554 
555     /*package*/ static final String CAPTURE_STACK_TRACE_PROPERTY = SOAPFaultBuilder.class.getName()+".captureStackTrace";
556 
557     static {
558         boolean tmpVal = false;
559         try {
560             tmpVal = Boolean.getBoolean(CAPTURE_STACK_TRACE_PROPERTY);
561         } catch (SecurityException e) {
562             // ignore
563         }
564         captureStackTrace = tmpVal;
565         JAXB_CONTEXT = createJAXBContext();
566     }
567 
568     private static JAXBContext createJAXBContext() {
569 
570         // in jdk runtime doPrivileged is necessary since JAX-WS internal classes are in restricted packages
571         if (isJDKRuntime()) {
572             Permissions permissions = new Permissions();
573             permissions.add(new RuntimePermission("accessClassInPackage.com.sun." + "xml.internal.ws.fault"));
574             permissions.add(new ReflectPermission("suppressAccessChecks"));
575             return AccessController.doPrivileged(
576                     new PrivilegedAction<JAXBContext>() {
577                         @Override
578                         public JAXBContext run() {
579                             try {
580                                 return JAXBContext.newInstance(SOAP11Fault.class, SOAP12Fault.class);
581                             } catch (JAXBException e) {
582                                 throw new Error(e);
583                             }
584                         }
585                     },
586                     new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, permissions)})
587             );
588 
589         } else {
590             try {
591                 return JAXBContext.newInstance(SOAP11Fault.class, SOAP12Fault.class);
592             } catch (JAXBException e) {
593                 throw new Error(e);
594             }
595         }
596     }
597 
598     private static boolean isJDKRuntime() {
599         return SOAPFaultBuilder.class.getName().contains("internal");
600     }
601 }