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