// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.api.ads.common.lib.soap.jaxws; import com.google.api.ads.common.lib.conf.AdsApiConfiguration; import com.google.api.ads.common.lib.exception.ServiceException; import com.google.api.ads.common.lib.utils.NodeExtractor; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import org.w3c.dom.Node; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; /** * SOAP Context Handler for use with JAX-WS. Responsible for logging SOAP XML * messages and attaching implicit headers to the outgoing messages. * * This class is not thread-safe. Every SOAP client is expected to have its own * instance of this class and each SOAP call made by those clients has to be * atomic. */ public class JaxWsSoapContextHandler implements SOAPHandler<SOAPMessageContext> { private String lastSoapRequest; private String lastSoapResponse; private String lastRequestId; private String lastServiceCalled; private String lastOperationCalled; private Set<SOAPElement> soapHeaders = new HashSet<SOAPElement>(); private final NodeExtractor nodeExtractor; private final ImmutableList<String> requestIdXPathComponents; /** * @param nodeExtractor required; used to extract request ID from SOAP responses. */ public JaxWsSoapContextHandler( NodeExtractor nodeExtractor, AdsApiConfiguration adsApiConfiguration) { this.nodeExtractor = nodeExtractor; String requestIdXPath = adsApiConfiguration.getRequestIdXPath(); if (!Strings.isNullOrEmpty(requestIdXPath)) { requestIdXPathComponents = ImmutableList.<String>copyOf( Splitter.on('/').split(requestIdXPath)); } else { requestIdXPathComponents = ImmutableList.<String>of(); } } /** * Captures pertinent information from SOAP messages exchanged by the SOAP * service this handler is attached to. Also responsible for placing custom * (implicit) SOAP headers on outgoing messages. * * @see SOAPHandler#handleMessage(MessageContext) * @param context the context of the SOAP message passing through this handler * @return whether this SOAP interaction should continue */ @Override public boolean handleMessage(SOAPMessageContext context) { if ((Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { SOAPMessage soapMessage = context.getMessage(); try { SOAPHeader soapHeader = soapMessage.getSOAPHeader(); if (soapHeader == null) { soapHeader = soapMessage.getSOAPPart().getEnvelope().addHeader(); } for (SOAPElement header : soapHeaders) { soapHeader.addChildElement(header); } } catch (SOAPException e) { throw new ServiceException("Error setting SOAP headers on outbound message.", e); } captureServiceAndOperationNames(context); } captureSoapXml(context); return true; } /** * Extracts the name of the web service and SOAP operation from a message. * * @param context the context of the SOAP message passing through this handler */ @VisibleForTesting void captureServiceAndOperationNames(SOAPMessageContext context) { lastServiceCalled = ((QName) context.get(MessageContext.WSDL_SERVICE)).getLocalPart(); try { lastOperationCalled = context.getMessage().getSOAPBody().getFirstChild().getLocalName(); } catch (SOAPException e) { lastOperationCalled = ""; // Fail silently. The logs will be missing the operation name for this interaction. } } /** * Returns the name of the last SOAP operation through this handler. */ public String getLastOperationCalled() { return lastOperationCalled; } /** * Returns the name of the last SOAP service contacted through handler. */ public String getLastServiceCalled() { return lastServiceCalled; } /** * Captures pertinent information from a message representing a SOAP fault. * * @see SOAPHandler#handleFault(MessageContext) * @param context the context of the SOAP message passing through this handler * @return whether this SOAP interaction should continue */ @Override public boolean handleFault(SOAPMessageContext context) { captureSoapXml(context); return true; } /** * Captures the raw XML message behind a SOAP interaction. * * @param context the context of the SOAP message passing through this handler */ private void captureSoapXml(SOAPMessageContext context) { SOAPMessage message = context.getMessage(); String soapXml = ""; try { OutputStream outputStream = new ByteArrayOutputStream(); message.writeTo(outputStream); soapXml = outputStream.toString(); SOAPHeader soapHeader = message.getSOAPHeader(); if ((!(Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) && soapHeader != null && !requestIdXPathComponents.isEmpty()) { Node requestIdNode = nodeExtractor.extractNode(soapHeader, requestIdXPathComponents); if (requestIdNode != null) { lastRequestId = requestIdNode.getFirstChild().getNodeValue(); } } } catch (IOException e) { soapXml = "Exception logging SOAP message: " + e; } catch (SOAPException e) { soapXml = "Exception logging SOAP message: " + e; } if ((Boolean) context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { lastSoapRequest = soapXml; } else { lastSoapResponse = soapXml; } } /** * Returns the last SOAP request XML message handled by this object. */ public String getLastRequestXml() { return lastSoapRequest; } /** * Returns the last SOAP response XML message handled by this object. */ public String getLastResponseXml() { return lastSoapResponse; } /** * Returns the request ID from the last SOAP response XML message handled by this object. */ public String getLastRequestId() { return lastRequestId; } /** * Adds a header to the list of SOAP request headers. * * @param namespace the namespace the header belongs to * @param headerName the name of the header element * @param headerValue the value of the header element */ public void addHeader(String namespace, String headerName, SOAPElement headerValue) { this.soapHeaders.add(headerValue); } /** * Clears all the headers set in this handler. */ public void clearHeaders() { this.soapHeaders.clear(); } /** * Returns the set of SOAP headers added to this handler. */ public Set<SOAPElement> getAddedHeaders() { return soapHeaders; } /** * @see SOAPHandler#getHeaders() */ @Override public Set<QName> getHeaders() { return null; } /** * @see SOAPHandler#close(MessageContext) */ @Override public void close(MessageContext messageContext) {} }