Is there a guaranteed way to get operation name in a custom soap handler?
Asked Answered
W

4

8

I have a custom SOAP message handler for incoming messages that will run different code based on which operation is being called. My first try to get the operation name looked something liket this:

public boolean handleMessage(SOAPMessageContext context)
{
    String op = context.get(MessageContext.WSDL_OPERATION);
    ...

This failed because the property MessageContext.WSDL_OPERATION appears to never be set. I then tried using this:

public boolean handleMessage(SOAPMessageContext context)
{
    Map<?, ?> headers = (Map<?, ?>)context.get(MessageContext.HTTP_REQUEST_HEADERS);    
    ArrayList<String> SOAPAction = ((ArrayList<String>) headers.get("SOAPAction"));
    String opName = SOAPAction.get(0);
    //opName will be formatted like "urn#myOperation", so the prefix must be removed
    opName = ((opName.replace("\"","").split("#"))[1]);

This works, but I'm concerned there could be situations where the header property "SOAPAction" isn't set (or doesn't even exist), or does not have the value that I'm expecting it to. I'm also a little concerned because I don't know if this is an "official" way to get the operation name - I figured it out by looking at the contents of context in the debugger.

Is there any better way to get the operation name when handling incoming SOAP messages?

Wilmoth answered 23/11, 2012 at 20:20 Comment(4)
I'm almost positive that the SOAPAction header has been removed from the SOAP HTTP binding specification. So don't expect it to be there for applications that conform to the most recent version of SOAP.Eucken
Reading up a bit, the SOAPAction header was removed in SOAP 1.2. It was replaced by the action parameter of the application/soap+xml media type (w3.org/TR/2007/REC-soap12-part0-20070427). However, this parameter is optional, and according to RFC 3902, "there is no mechanism for automatically computing the value based on the SOAP envelope...the value has to be determined out of band."Eucken
@NathanD.Ryan: Ugh... does this mean I need to separate the operations so that there's only one operation per service, just to ensure the handler knows which operation its working with? Our webservice is using SOAP 1.1, so I guess it will work until that changes...Wilmoth
I'm afraid that I can't give you a definitive answer, but it certainly seems that way. At least, there doesn't seem to be any internal mechanism by which to guarantee a determination of the SOAP action.Eucken
W
4

You could call body.getElementName().getLocalName() to retrieve the name of SOAP body element of the message payload. It's a little bit verbose and manual but it works. You could have the following in your handler

if ((boolean) context.get(MessageContext.MESSAGE_INBOUND_PROPERTY){ //for requests only
            SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //get the SOAP Message envelope
                SOAPBody body = msg.getBody();
            String operationName = body.getChildNodes().item(1).getLocalName();
}

The result of the above code is guaranteed to carry the name of the operation as specified in your WSDL

EDIT: This solution is based solely on the condition that the web service is implemented as document/literal-wrapped or RPC/literal

Wenger answered 24/11, 2012 at 16:42 Comment(6)
@Wilmoth As far as I know, yes. The value you'll be picking will be from the SOAP body part of the message payload, which is guaranteed to always have a value, or else the entire request is invalid. Give it a go yourselfWenger
Are you sure that code is correct? I tried it and I get "Body" as the value for operationName. The string-value for msg is: <soapenv:Body xmlns:soapenv="schemas.xmlsoap.org/soap/envelope" xmlns:head=(namespaces delcared here)> <b:CancelOperationRequest> <head:messageHeader>... So if I were to get the next child of body, and get its name, it would probably be "CancelOperationRequest" but that is NOT the name of the operation, it is the name of the request type (the operation's name, in this case, is "cancelOperation").Wilmoth
@FrustratedWithFormsDesigner, my oversight, you're correct, you should go to the element immediately after <soap:body/> to hit the actual message payload. To state the obvious CancelOperationRequest is an auto-generated part name from the wsdl which you are at liberty to either explicitly control via the operationName attribute of the @WebParam annotation for your WS name or you manually carry out the String transformations (substring the "Request" bit away from the envelope). Beyond those two, you're on your own. I did warn that this is verbose ;)Wenger
@FrustratedWithFormsDesigner, out of curiosity, why do you need named access to the WS operation? The WSIT Basic profile tries to discourage such dependency by discouraging the use of the SOAP action attribute. Perhaps an alternative approach to solving your issue?Wenger
Currently, I'm supposed to perform schema validation on incoming messages based on operation name. The other option is to put ALL operation schemas into the same file for one webservice, but I would rather have the finer-grained control of knowing which operation I'm dealing with (I think it may make things easier in the future for a couple cases).Wilmoth
Hmm I'll have to have a look at what's set in @WebParam.Wilmoth
D
5

I'm very late to this party but I tried to do this over the past week. The accepted answer doesn't actually work for every JAX-WS implementation (at least not that I tried).

I have been trying to make this work on standalone Metro in my development environment but also using Axis2 bundled with WebSphere 7 in a real environment.

I found the following works on Metro:

String operationName = body.getChildNodes().item(0).getLocalName();

and the following works on Axis2:

String operationName = body.getChildNodes().item(1).getLocalName();

What is happening is that Axis2 inserts a Node of type Text into the Body as the first child but Metro doesn't. This text node returns a null local name. My solution was to do the following:

NodeList nodes = body.getChildNodes();

// -- Loop over the nodes in the body.
for (int i=0; i<nodes.getLength(); i++) {
  Node item = nodes.item(i);

  // -- The first node of type SOAPBodyElement will be
  // -- what we're after.
  if (item instanceof SOAPBodyElement) {
    return item.getLocalName();
  }
}

As described in the comments we're actually looking for the first node of type SOAPBodyElement. Hopefully that will help out anyone else looking at this in the future.

Dromous answered 21/2, 2014 at 19:3 Comment(1)
That's still the name of the Message, not the Operation.Dispersant
W
4

You could call body.getElementName().getLocalName() to retrieve the name of SOAP body element of the message payload. It's a little bit verbose and manual but it works. You could have the following in your handler

if ((boolean) context.get(MessageContext.MESSAGE_INBOUND_PROPERTY){ //for requests only
            SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //get the SOAP Message envelope
                SOAPBody body = msg.getBody();
            String operationName = body.getChildNodes().item(1).getLocalName();
}

The result of the above code is guaranteed to carry the name of the operation as specified in your WSDL

EDIT: This solution is based solely on the condition that the web service is implemented as document/literal-wrapped or RPC/literal

Wenger answered 24/11, 2012 at 16:42 Comment(6)
@Wilmoth As far as I know, yes. The value you'll be picking will be from the SOAP body part of the message payload, which is guaranteed to always have a value, or else the entire request is invalid. Give it a go yourselfWenger
Are you sure that code is correct? I tried it and I get "Body" as the value for operationName. The string-value for msg is: <soapenv:Body xmlns:soapenv="schemas.xmlsoap.org/soap/envelope" xmlns:head=(namespaces delcared here)> <b:CancelOperationRequest> <head:messageHeader>... So if I were to get the next child of body, and get its name, it would probably be "CancelOperationRequest" but that is NOT the name of the operation, it is the name of the request type (the operation's name, in this case, is "cancelOperation").Wilmoth
@FrustratedWithFormsDesigner, my oversight, you're correct, you should go to the element immediately after <soap:body/> to hit the actual message payload. To state the obvious CancelOperationRequest is an auto-generated part name from the wsdl which you are at liberty to either explicitly control via the operationName attribute of the @WebParam annotation for your WS name or you manually carry out the String transformations (substring the "Request" bit away from the envelope). Beyond those two, you're on your own. I did warn that this is verbose ;)Wenger
@FrustratedWithFormsDesigner, out of curiosity, why do you need named access to the WS operation? The WSIT Basic profile tries to discourage such dependency by discouraging the use of the SOAP action attribute. Perhaps an alternative approach to solving your issue?Wenger
Currently, I'm supposed to perform schema validation on incoming messages based on operation name. The other option is to put ALL operation schemas into the same file for one webservice, but I would rather have the finer-grained control of knowing which operation I'm dealing with (I think it may make things easier in the future for a couple cases).Wilmoth
Hmm I'll have to have a look at what's set in @WebParam.Wilmoth
I
3

The SOAPMessageContext contains this information and can be retrieved super easily like this:

public boolean handleMessage(SOAPMessageContext msgContext) {
    QName svcn = (QName) smc.get(SOAPMessageContext.WSDL_SERVICE);      
    QName opn = (QName) smc.get(SOAPMessageContext.WSDL_OPERATION);
    System.out.prinln("WSDL Service="+ svcn.getLocalPart());
    System.out.prinln("WSDL Operation="+ opn.getLocalPart());

    return true;
}
Iridium answered 16/5, 2013 at 17:22 Comment(7)
I'd already tried using String op = context.get(MessageContext.WSDL_OPERATION); which failed (for some reason, it never had a value), which is why I posted my question in the first place.Wilmoth
Are you sure your MessageContext is a SOAPMessageContext? If it is, that shouldn't ever fail. I have had it fail though when it was a generic message context. Not sure if you have tried this nor if you can do this given your situation, but make sure your handler implements SOAPHandler<SOAPMessageContext> rather then Handler.Iridium
Also, make sure you are evaluating those parameters only on the WebService side of the communication (rather then the client) when MessageContext.MESSAGE_INBOUND_PROPERTY is set to true.Iridium
Yes. It was running in the server process, and only for incoming messages.Wilmoth
WebSphere 8.0.0.10 returns null for the WSDL_OPERATION.Dispersant
From a contract last perspective; I was able to solve this by explicitly defining the "action" value. i.e. @WebMethod(action = "mySOAPAction")Uxoricide
Thanks so much for sharing the code. It works perfectly !!! (I am using WebLogic 12.1.3). It works fine for inbound and outbound messages in my case.Anastasiaanastasie
M
1

in case if someone searches for "elegant" way to get needed properties use

    for(Map.Entry e : soapMessageContext.entrySet()){
            log.info("entry:"+ e.getKey() + " = " + e.getValue());
    }

then decide what info you need and get it!

soapMessageContext.get(YOUR_DESIRED_KEY);
Mcintire answered 12/12, 2015 at 12:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.