001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.fault;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.istack.Nullable;
041: import com.sun.xml.bind.api.Bridge;
042: import com.sun.xml.bind.api.JAXBRIContext;
043: import com.sun.xml.ws.api.SOAPVersion;
044: import com.sun.xml.ws.api.message.Message;
045: import com.sun.xml.ws.api.model.CheckedException;
046: import com.sun.xml.ws.api.model.ExceptionType;
047: import com.sun.xml.ws.encoding.soap.SOAP12Constants;
048: import com.sun.xml.ws.encoding.soap.SOAPConstants;
049: import com.sun.xml.ws.encoding.soap.SerializationException;
050: import com.sun.xml.ws.message.jaxb.JAXBMessage;
051: import com.sun.xml.ws.model.CheckedExceptionImpl;
052: import com.sun.xml.ws.model.JavaMethodImpl;
053: import com.sun.xml.ws.util.DOMUtil;
054: import com.sun.xml.ws.util.StringUtils;
055: import org.w3c.dom.Document;
056: import org.w3c.dom.Element;
057: import org.w3c.dom.Node;
058:
059: import javax.xml.bind.JAXBContext;
060: import javax.xml.bind.JAXBException;
061: import javax.xml.namespace.QName;
062: import javax.xml.soap.SOAPFault;
063: import javax.xml.transform.dom.DOMResult;
064: import javax.xml.ws.ProtocolException;
065: import javax.xml.ws.WebServiceException;
066: import javax.xml.ws.soap.SOAPFaultException;
067: import java.lang.reflect.Constructor;
068: import java.lang.reflect.Field;
069: import java.lang.reflect.Method;
070: import java.util.Iterator;
071: import java.util.Map;
072: import java.util.logging.Level;
073: import java.util.logging.Logger;
074:
075: /**
076: * Base class that represents SOAP 1.1 or SOAP 1.2 fault. This class can be used by the invocation handlers to create
077: * an Exception from a received messge.
078: *
079: * @author Vivek Pandey
080: */
081: public abstract class SOAPFaultBuilder {
082:
083: /**
084: * 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
085: * a protocol specific exception
086: */
087: abstract DetailType getDetail();
088:
089: abstract void setDetail(DetailType detailType);
090:
091: /**
092: * gives the fault string that can be used to create an {@link Exception}
093: */
094: abstract String getFaultString();
095:
096: /**
097: * This should be called from the client side to throw an {@link Exception} for a given soap mesage
098: */
099: public Throwable createException(
100: Map<QName, CheckedExceptionImpl> exceptions)
101: throws JAXBException {
102: DetailType dt = getDetail();
103: Node detail = null;
104: if (dt != null)
105: detail = dt.getDetail(0);
106:
107: //return ProtocolException if the detail is not present or there is no checked exception
108: if (detail == null || exceptions == null) {
109: // No soap detail, doesnt look like its a checked exception
110: // throw a protocol exception
111: return attachServerException(getProtocolException());
112: }
113:
114: //check if the detail is a checked exception, if not throw a ProtocolException
115: QName detailName = new QName(detail.getNamespaceURI(), detail
116: .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(
131: String.class, (Class) ce.getDetailType().type);
132: Exception exception = (Exception) constructor.newInstance(
133: getFaultString(), getJAXBObject(detail, ce));
134: return attachServerException(exception);
135: } catch (Exception e) {
136: throw new WebServiceException(e);
137: }
138: }
139:
140: /**
141: * To be called to convert a {@link ProtocolException} and faultcode for a given {@link SOAPVersion} in to a {@link Message}.
142: *
143: * @param soapVersion {@link SOAPVersion#SOAP_11} or {@link SOAPVersion#SOAP_12}
144: * @param ex a ProtocolException
145: * @param faultcode soap faultcode. Its ignored if the {@link ProtocolException} instance is {@link SOAPFaultException} and it has a
146: * faultcode present in the underlying {@link SOAPFault}.
147: * @return {@link Message} representing SOAP fault
148: */
149: public static @NotNull
150: Message createSOAPFaultMessage(@NotNull
151: SOAPVersion soapVersion, @NotNull
152: ProtocolException ex, @Nullable
153: QName faultcode) {
154: Object detail = getFaultDetail(null, ex);
155: if (soapVersion == SOAPVersion.SOAP_12)
156: return createSOAP12Fault(soapVersion, ex, detail, null,
157: faultcode);
158: return createSOAP11Fault(soapVersion, ex, detail, null,
159: faultcode);
160: }
161:
162: /**
163: * To be called by the server runtime in the situations when there is an Exception that needs to be transformed in
164: * to a soapenv:Fault payload.
165: *
166: * @param ceModel {@link CheckedExceptionImpl} model that provides useful informations such as the detail tagname
167: * and the Exception associated with it. Caller of this constructor should get the CheckedException
168: * model by calling {@link JavaMethodImpl#getCheckedException(Class)}, where
169: * Class is t.getClass().
170: * <p>
171: * If its null then this is not a checked exception and in that case the soap fault will be
172: * serialized only from the exception as described below.
173: * @param ex Exception that needs to be translated into soapenv:Fault, always non-null.
174: * <ul>
175: * <li>If t is instance of {@link SOAPFaultException} then its serilaized as protocol exception.
176: * <li>If t.getCause() is instance of {@link SOAPFaultException} and t is a checked exception then
177: * the soap fault detail is serilaized from t and the fault actor/string/role is taken from t.getCause().
178: * </ul>
179: * @param soapVersion non-null
180: */
181: public static Message createSOAPFaultMessage(
182: SOAPVersion soapVersion, CheckedExceptionImpl ceModel,
183: Throwable ex) {
184: Object detail = getFaultDetail(ceModel, ex);
185: if (soapVersion == SOAPVersion.SOAP_12)
186: return createSOAP12Fault(soapVersion, ex, detail, ceModel,
187: null);
188: return createSOAP11Fault(soapVersion, ex, detail, ceModel, null);
189: }
190:
191: /**
192: * Server runtime will call this when there is some internal error not resulting from an exception.
193: *
194: * @param soapVersion {@link SOAPVersion#SOAP_11} or {@link SOAPVersion#SOAP_12}
195: * @param faultString must be non-null
196: * @param faultCode For SOAP 1.1, it must be one of
197: * <ul>
198: * <li>{@link SOAPVersion#faultCodeClient}
199: * <li>{@link SOAPVersion#faultCodeServer}
200: * <li>{@link SOAPConstants#FAULT_CODE_MUST_UNDERSTAND}
201: * <li>{@link SOAPConstants#FAULT_CODE_VERSION_MISMATCH}
202: * </ul>
203: *
204: * For SOAP 1.2
205: * <ul>
206: * <li>{@link SOAPVersion#faultCodeClient}
207: * <li>{@link SOAPVersion#faultCodeServer}
208: * <li>{@link SOAP12Constants#FAULT_CODE_MUST_UNDERSTAND}
209: * <li>{@link SOAP12Constants#FAULT_CODE_VERSION_MISMATCH}
210: * <li>{@link SOAP12Constants#FAULT_CODE_DATA_ENCODING_UNKNOWN}
211: * </ul>
212: * @return non-null {@link Message}
213: */
214: public static Message createSOAPFaultMessage(
215: SOAPVersion soapVersion, String faultString, QName faultCode) {
216: if (faultCode == null)
217: faultCode = getDefaultFaultCode(soapVersion);
218: return createSOAPFaultMessage(soapVersion, faultString,
219: faultCode, null);
220: }
221:
222: public static Message createSOAPFaultMessage(
223: SOAPVersion soapVersion, SOAPFault fault) {
224: switch (soapVersion) {
225: case SOAP_11:
226: return JAXBMessage.create(JAXB_CONTEXT, new SOAP11Fault(
227: fault), soapVersion);
228: case SOAP_12:
229: return JAXBMessage.create(JAXB_CONTEXT, new SOAP12Fault(
230: fault), soapVersion);
231: default:
232: throw new AssertionError();
233: }
234: }
235:
236: private static Message createSOAPFaultMessage(
237: SOAPVersion soapVersion, String faultString,
238: QName faultCode, Element detail) {
239: switch (soapVersion) {
240: case SOAP_11:
241: return JAXBMessage.create(JAXB_CONTEXT, new SOAP11Fault(
242: faultCode, faultString, null, detail), soapVersion);
243: case SOAP_12:
244: return JAXBMessage.create(JAXB_CONTEXT, new SOAP12Fault(
245: 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
256: Throwable t) {
257: if (t == null)
258: return;
259: if (!captureStackTrace)
260: return; // feature disabled
261:
262: try {
263: Document d = DOMUtil.createDom();
264: ExceptionBean.marshal(t, d);
265:
266: DetailType detail = getDetail();
267: if (detail == null)
268: setDetail(detail = new DetailType());
269:
270: detail.getDetails().add(d.getDocumentElement());
271: } catch (JAXBException e) {
272: // this should never happen
273: logger.log(Level.WARNING,
274: "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)
284: return t; // no details
285:
286: for (Element n : detail.getDetails()) {
287: if (ExceptionBean.isStackTraceXml(n)) {
288: try {
289: t.initCause(ExceptionBean.unmarshal(n));
290: } catch (JAXBException e) {
291: // perhaps incorrectly formatted XML.
292: logger
293: .log(
294: Level.WARNING,
295: "Unable to read the capture stack trace in the fault",
296: e);
297: }
298: return t;
299: }
300: }
301:
302: return t;
303: }
304:
305: abstract protected Throwable getProtocolException();
306:
307: private Object getJAXBObject(Node jaxbBean, CheckedException ce)
308: throws JAXBException {
309: Bridge bridge = ce.getBridge();
310: return bridge.unmarshal(jaxbBean);
311: }
312:
313: private Exception createUserDefinedException(CheckedExceptionImpl ce) {
314: Class exceptionClass = ce.getExceptionClass();
315: Class detailBean = ce.getDetailBean();
316: try {
317: Node detailNode = getDetail().getDetails().get(0);
318: Object jaxbDetail = getJAXBObject(detailNode, ce);
319: Constructor exConstructor;
320: try {
321: exConstructor = exceptionClass.getConstructor(
322: String.class, detailBean);
323: return (Exception) exConstructor.newInstance(
324: getFaultString(), jaxbDetail);
325: } catch (NoSuchMethodException e) {
326: exConstructor = exceptionClass
327: .getConstructor(String.class);
328: return (Exception) exConstructor
329: .newInstance(getFaultString());
330: }
331: } catch (Exception e) {
332: throw new WebServiceException(e);
333: }
334: }
335:
336: private static String getWriteMethod(Field f) {
337: return "set" + StringUtils.capitalize(f.getName());
338: }
339:
340: private static Object getFaultDetail(CheckedExceptionImpl ce,
341: Throwable exception) {
342: if (ce == null)
343: return null;
344: if (ce.getExceptionType().equals(ExceptionType.UserDefined)) {
345: return createDetailFromUserDefinedException(ce, exception);
346: }
347: try {
348: Method m = exception.getClass().getMethod("getFaultInfo");
349: return m.invoke(exception);
350: } catch (Exception e) {
351: throw new SerializationException(e);
352: }
353: }
354:
355: private static Object createDetailFromUserDefinedException(
356: CheckedExceptionImpl ce, Object exception) {
357: Class detailBean = ce.getDetailBean();
358: Field[] fields = detailBean.getDeclaredFields();
359: try {
360: Object detail = detailBean.newInstance();
361: for (Field f : fields) {
362: Method em = exception.getClass().getMethod(
363: getReadMethod(f));
364: Method sm = detailBean.getMethod(getWriteMethod(f), em
365: .getReturnType());
366: sm.invoke(detail, em.invoke(exception));
367: }
368: return detail;
369: } catch (Exception e) {
370: throw new SerializationException(e);
371: }
372: }
373:
374: private static String getReadMethod(Field f) {
375: if (f.getType().isAssignableFrom(boolean.class))
376: return "is" + StringUtils.capitalize(f.getName());
377: return "get" + StringUtils.capitalize(f.getName());
378: }
379:
380: private static Message createSOAP11Fault(SOAPVersion soapVersion,
381: Throwable e, Object detail, CheckedExceptionImpl ce,
382: QName faultCode) {
383: SOAPFaultException soapFaultException = null;
384: String faultString = null;
385: String faultActor = null;
386: Throwable cause = e.getCause();
387: if (e instanceof SOAPFaultException) {
388: soapFaultException = (SOAPFaultException) e;
389: } else if (cause != null && cause instanceof SOAPFaultException) {
390: soapFaultException = (SOAPFaultException) e.getCause();
391: }
392: if (soapFaultException != null) {
393: QName soapFaultCode = soapFaultException.getFault()
394: .getFaultCodeAsQName();
395: if (soapFaultCode != null)
396: faultCode = soapFaultCode;
397:
398: faultString = soapFaultException.getFault()
399: .getFaultString();
400: faultActor = soapFaultException.getFault().getFaultActor();
401: }
402:
403: if (faultCode == null) {
404: faultCode = getDefaultFaultCode(soapVersion);
405: }
406:
407: if (faultString == null) {
408: faultString = e.getMessage();
409: if (faultString == null) {
410: faultString = e.toString();
411: }
412: }
413: Element detailNode = null;
414: if (detail == null && soapFaultException != null) {
415: detailNode = soapFaultException.getFault().getDetail();
416: } else if (ce != null) {
417: try {
418: DOMResult dr = new DOMResult();
419: ce.getBridge().marshal(detail, dr);
420: detailNode = (Element) dr.getNode().getFirstChild();
421: } catch (JAXBException e1) {
422: //Should we throw Internal Server Error???
423: faultString = e.getMessage();
424: faultCode = getDefaultFaultCode(soapVersion);
425: }
426: }
427: SOAP11Fault soap11Fault = new SOAP11Fault(faultCode,
428: faultString, faultActor, detailNode);
429: soap11Fault.captureStackTrace(e);
430:
431: return JAXBMessage.create(JAXB_CONTEXT, soap11Fault,
432: soapVersion);
433: }
434:
435: private static Message createSOAP12Fault(SOAPVersion soapVersion,
436: Throwable e, Object detail, CheckedExceptionImpl ce,
437: QName faultCode) {
438: SOAPFaultException soapFaultException = null;
439: CodeType code = null;
440: String faultString = null;
441: String faultRole = null;
442: Throwable cause = e.getCause();
443: if (e instanceof SOAPFaultException) {
444: soapFaultException = (SOAPFaultException) e;
445: } else if (cause != null && cause instanceof SOAPFaultException) {
446: soapFaultException = (SOAPFaultException) e.getCause();
447: }
448: if (soapFaultException != null) {
449: SOAPFault fault = soapFaultException.getFault();
450: QName soapFaultCode = fault.getFaultCodeAsQName();
451: if (soapFaultCode != null) {
452: faultCode = soapFaultCode;
453: code = new CodeType(faultCode);
454: Iterator iter = fault.getFaultSubcodes();
455: boolean first = true;
456: SubcodeType subcode = null;
457: while (iter.hasNext()) {
458: QName value = (QName) iter.next();
459: if (first) {
460: SubcodeType sct = new SubcodeType(value);
461: code.setSubcode(sct);
462: subcode = sct;
463: first = false;
464: continue;
465: }
466: subcode = fillSubcodes(subcode, value);
467: }
468: }
469: faultString = soapFaultException.getFault()
470: .getFaultString();
471: faultRole = soapFaultException.getFault().getFaultActor();
472: }
473:
474: if (faultCode == null) {
475: faultCode = getDefaultFaultCode(soapVersion);
476: code = new CodeType(faultCode);
477: } else if (code == null) {
478: code = new CodeType(faultCode);
479: }
480:
481: if (faultString == null) {
482: faultString = e.getMessage();
483: if (faultString == null) {
484: faultString = e.toString();
485: }
486: }
487:
488: ReasonType reason = new ReasonType(faultString);
489: Element detailNode = null;
490: if (detail == null && soapFaultException != null) {
491: detailNode = soapFaultException.getFault().getDetail();
492: } else if (detail != null) {
493: try {
494: DOMResult dr = new DOMResult();
495: ce.getBridge().marshal(detail, dr);
496: detailNode = (Element) dr.getNode().getFirstChild();
497: } catch (JAXBException e1) {
498: //Should we throw Internal Server Error???
499: faultString = e.getMessage();
500: faultCode = getDefaultFaultCode(soapVersion);
501: }
502: }
503: DetailType detailType = null;
504: if (detailNode != null)
505: detailType = new DetailType(detailNode);
506: SOAP12Fault soap12Fault = new SOAP12Fault(code, reason, null,
507: faultRole, detailType);
508: soap12Fault.captureStackTrace(e);
509:
510: return JAXBMessage.create(JAXB_CONTEXT, soap12Fault,
511: soapVersion);
512: }
513:
514: private static SubcodeType fillSubcodes(SubcodeType parent,
515: QName value) {
516: SubcodeType newCode = new SubcodeType(value);
517: parent.setSubcode(newCode);
518: return newCode;
519: }
520:
521: private static QName getDefaultFaultCode(SOAPVersion soapVersion) {
522: return soapVersion.faultCodeServer;
523: }
524:
525: /**
526: * Parses a fault {@link Message} and returns it as a {@link SOAPFaultBuilder}.
527: *
528: * @return always non-null valid object.
529: * @throws JAXBException if the parsing fails.
530: */
531: public static SOAPFaultBuilder create(Message msg)
532: throws JAXBException {
533: return msg.readPayloadAsJAXB(JAXB_CONTEXT.createUnmarshaller());
534: }
535:
536: /**
537: * This {@link JAXBContext} can handle SOAP 1.1/1.2 faults.
538: */
539: private static final JAXBRIContext JAXB_CONTEXT;
540:
541: private static final Logger logger = Logger
542: .getLogger(SOAPFaultBuilder.class.getName());
543:
544: /**
545: * Set to false if you don't want the generated faults to have stack trace in it.
546: */
547: public static boolean captureStackTrace;
548:
549: /*package*/static final String CAPTURE_STACK_TRACE_PROPERTY = SOAPFaultBuilder.class
550: .getName()
551: + ".disableCaptureStackTrace";
552:
553: static {
554: try {
555: captureStackTrace = System
556: .getProperty(CAPTURE_STACK_TRACE_PROPERTY) == null;
557: } catch (SecurityException e) {
558: // ignore
559: }
560:
561: try {
562: JAXB_CONTEXT = (JAXBRIContext) JAXBContext.newInstance(
563: SOAP11Fault.class, SOAP12Fault.class);
564: } catch (JAXBException e) {
565: throw new Error(e); // this must be a bug in our code
566: }
567: }
568: }
|